Tuesday, October 23, 2018

Part 5 - Linking ViewModels to your Mobile App

This is part 5 of a multi-part tutorial of building a mobile application step by step from start to finish.  If you have just landed here, I encourage you to take the journey with me through building a mobile application.  I’ll show you how easy it is to build and maintain an application that can be hosted as a web application as well as a mobile application.  If you are here for part 5, we’re ready to discuss ViewModels.

The main goals I have for using ViewModels between the client and server is to limit complexity and overexposure of data on the client side.  The ViewModel will be an object (or data model) that supports the user interface (or view); thus a View-Model.  Our ViewModels will exist on the client and server side applications and make the transmission of data between the client and server seamless.  If you are familiar with other methods, like mapping, exposing models directly, or sending complex composite objects, I recommend giving simple ViewModels a try.  They may seem like more work, but they provide clarity, consistency, and security that is difficult otherwise to achieve.
With all of that being said, let’s get to business with our first ViewModel.

Open Visual Studio Code to the invoicing application we have been working on and create a new folder called view-models under the src folder.

The first ViewModel we’ll create will be for the Login screen (src/pages/home/home.html).  If you recall, this page includes an email address input and a password input.

Right-click on the view-models folder and add a New File and name the file LoginViewModel.ts.  We’ll add the two fields that we need to accept input from on the login screen itself.  These should both be the String data type.

image

src/view-models/LoginViewModel.ts
image

Let’s now connect this up to our login component.  There are two ways to connect or bind to components.  We’ll be covering the more straight forward ngModel binding method.  Save your ViewModel and open the src/pages/home/home.ts file.

Add a property before the constructor to hold an object reference to the new ViewModel that we just created.  Using the intellisense, you can get the appropriate imports with little effort.

src/pages/home/home.ts
image

Next we’ll implement the interface named OnInit.  Type “implements OnInit” in the class declaration as shown below.  The intellisense will add OnInit to the top import and you will be required to implement this interface by creating an ngOnInit() { } method.  If you aren’t familiar with the dynamics of interfaces, you may want to do a quick google search.  The ngOnInit method will be executed automatically during the creation of the login component as part of the component’s life cycle.

image

We’ll then create our LoginViewModel object to populate our loginData property.

image

Using the ngOnInit() method in this case may be overkill, but this should be used when we start pulling data from the server.  This will allow the page to begin loading while data is being requested from the server and this should be the standard place we request data when a component is loaded.  To make sure the data is captured accurately from the form, let’s log the object in the Login button.  Add a simple console.log call in the login method as shown below.

image

Next, we’ll bind the inputs to the loginData object reference.  Open the src/pages/home/home.html file so we can add the data bindings.  On the email and password <ion-input> components we’ll add a binding attribute for ngModel.  To do this, add [(ngModel)]="property.field" to each of the inputs.  The "[ ]"s indicate property binding and the "( )"s indicate event binding.  In this case, the property is the value of the input box (like if we were pre-populating it), and the event is when something is typed into the input box such that our property values are updated.  I also made one additional change to the "type" property of the password input.  I changed the type to "password" such that any input is masked as it is typed.

image

Let’s run the application now to ensure the binding worked as expected.  Pull up the terminal with CTRL + Shift + ~ and type "ionic serve" to start the application.

image

Before clicking the Login button, press F12 to pull up the browser’s console window so we can see the loginData object that we had added code to log.  As you can see below, it logged the object as we expect.

image

As we continue on the other Views and create our ViewModels, we should include an "Id" numeric field such that we can identify and save any changes back to the database once the data has been delivered.  There is no need to create a ViewModel for a list of other ViewModels at this time, so we’ll only need a ClientViewModel and an InvoiceViewModel.  We may want to create a separate object in the future to optimize this listing.

src/view-models/ClientViewModel.ts
image

src/view-models/InvoiceViewModel.ts
image

Now, lets link these to the Views.

src/pages/client-detail/client-detail.ts (Add property, implement OnInit, add ngOnInit(), create new object)
image

src/pages/client-detail/client-detail.html (Add ngModel bindings to each input)
image

src/pages/invoice-detail/invoice-detail.ts
image

src/pages/invoice-detail/invoice-detail.html (only showing a subsection below)
image

For the Client List and Invoice List, we’ll populate some sample data so we can see that the list still works.  We’ll create an array of view models for each and populate it with the sample data and then use the *ngFor directive to repeat the <ion-item> component for each of the items in our array.

src/pages/client-list/client-list.ts
image

src/pages/client-list/client-list.html
image

src/pages/invoice-list/invoice-list.ts
image

src/pages/invoice-list/invoice-list.html
image

Restart the application and make sure everything is working as expected.

Now that we have the application in a somewhat working state, let’s have some fun and deploy it to an Android device in the next post.  A Mac is required for Apple deployment, so I will wait until the application is closer to production ready prior to running through this process.

Saturday, October 20, 2018

Part 4 - Building your Mobile App Interfaces

It is now time to setup the user interface for our invoicing application.  If you are just joining in, check out my prior posts to get setup.

Open Visual Studio Code.  Typically this application opens up to your last project, so if you are following along you will already be in the invoicing application from the prior post.

If you aren’t familiar with Angular or Ionic, I’ll go over some of the basics quick.  Ionic is built on top of the Angular framework.  The main difference between a vanilla Angular app and an Ionic app is how it is structured.  The main pieces of the Angular frameworks that we’ll be working with are Components, Directives and Services.  Components are simply segments of HTML with some Typescript code attached.  These Components are then arranged on the page (which is also a component) and rendered for the user.  Directives are applied to components to signal code to make modifications to the output of the component.  They are simply added to the component tags in the HTML similar to data attributes.  Services are Typescript classes that are injected into the constructor of your components (or pages) that provide functionality.  Some example services include making server calls, managing notifications, or performing common functionality that doesn’t have a visual representation.  Routes are another common Angular functionality that controls page navigation, however, Ionic has a navigation implementation that we’ll be using instead of Angular routes.

Navigation in Ionic is structured with a Stack data structure.  The root page is the first page pushed onto the stack when the application starts.  When navigating to another page, we simply push the page onto the stack.  To navigate back to the prior page, we pop the current page off the stack.  This navigation is controlled by a NavController service that is typically injected into the page components by default.

That should be enough to get rolling.  We'll start slow so you can see the process and progress quicker and more independently through the last few screens.  The code will currently only be contained in screenshots.  I may come back through in the future, but also believe it is good to type it out to gain some efficiency and familiarity with the framework.  With that said, let’s get down to business...

Login Page

Start by opening the following file src/pages/home/home.html
image

The first page we’ll create is the login page, which is the first page the user should arrive at when opening the application.  Remove the ion-header component by deleting the top structure from <ion-header> through </ion-header>.  This component builds the top header bar for the app, which isn't needed on the login page, but we’ll want it included on subsequent pages to eventually assist with navigation.  Next we’ll clear the content inside the <ion-content> component.

src/pages/home/home.html
image

Let’s recall what we wanted our Login page to look like and refer to some ionic docs for the necessary controls needed to build the login page.

Login Screen_thumb[1]

Grid: https://ionicframework.com/docs/components/#grid
Inputs: https://ionicframework.com/docs/components/#inputs
Buttons: https://ionicframework.com/docs/components/#buttons
Icons: https://ionicframework.com/docs/components/#icons

We’ll add a simple 3 column grid to help center the login page for larger screen displays.  We’ll leave the first and third columns empty and add our content to the center column.  The grid system is similar to the bootstrap layout, which consists of 12 columns.

For small devices, the first and third column we’ll have occupy the full 12 columns.  For medium displays, 2 columns and larger displays, 4 columns.

The middle column will occupy 12 columns on small displays, 8 on medium, and 4 on larger displays.

We’ll see the magic happen when we resize the display in the browser.  You should have the following so far.

src/pages/home/home.html
image

Next we’ll add the email and password inputs as well as a login button.  I am fond of the floating label controls from the Ionic docs and the look in other apps, so we’ll use those.  The secondary outline button sample will be our login button.  I want the login button to also go across the whole page, so we’ll add the block directive to it as well.

src/pages/home/home.html
image

Now let’s run this and see what we have and if we need to make any adjustments.  Press CTRL + Shift + ~ to pull up the terminal window and type:

ionic serve

image

Looks like we need some padding on the left and top side of the button and some sort of indication as to what application this is may be nice.  Let’s add a header <h1> tag to include a title and an <h3> tag to indicate this is a login page.  We’ll add some padding to those and center them as well.

src/pages/home/home.html
image

src/pages/home/home.html
image

No need to terminate the application, we can simply make the changes and save them, the application should automatically reload in the browser so we can quickly make the edits and see the results.

image

Looking good.  Now, we can move on to the next page, which will be our list of invoices.
Go to the terminal and press CTRL+C and terminate the application.  Next type the following to create a new page for the invoice list.

ionic g page invoiceList

This generates ‘g’ a new page named ‘invoice-list’.

image

You can now see the files in the left navigation panel of Visual Studio Code.

image

Before we start the application, we should link the Login button to this new screen so we can check our progress as we design the new page.  Add a call to the button to run an new login() method.  This is done as shown below.

src/pages/home/home.html
image

Next, we have to add the login method to the Typescript file for the home page.  Open the src/pages/home/home.ts file and add a method for login, just like you would define a javascript method.  Inside this method, make a call to the this.navCtrl.push, and pass in the new invoice list page.  Use intellisense to add the page such that the appropriate references are included in the home page as shown in the below example.  Press the tab key to accept the intellisense and notice the imports change at the top.  Be sure to reference the InvoiceListPage, and not the Module.

src/pages/home/home.ts - before
image

src/pages/home/home.ts - after
image

There is one last reference we need to add such that this new page is recognized by the Angular framework.  Open the file src/app/app.module.ts and add the InvoiceListPageModule to the list of imports.  Using the intellisense, it should also add the import line on line 9 in the image below.

src/app/app.module.ts
image

At this point, you can run ‘ionic serve’ from the terminal to start the application up again and make sure the login button takes us to the invoice list page.

Invoice List

Now you can open the src/pages/invoice-list/invoice-list.html file so we can build this interface.  Let’s recall what that looks like.

Invoice List[2]

Taking a look through the existing components in the Ionic docs, it looks like the avatar list will be the most appropriate starting point for this page.  We’ll also need a dropdown, so the ‘select’ component appears to be what we need for that.

Avatar List: https://ionicframework.com/docs/components/#avatar-list
Select: https://ionicframework.com/docs/components/#select

Let’s copy the sample code from the Avatar List example into the content section, remove the large block of comments at the top of the new page, and leave the header component on this page as we’ll be using it on the pages from here on out.  You’ll notice there is an <ion-list> component included in the sample code.  This allows scrolling within the application, which will be needed once we add a few invoices.  We’ll dummy some data up and add an image to our assets/imgs folder for the contact.

assets/imgs/person.png
person

Here is what I came up with first shot.

src/pages/invoice-list/invoice-list.html
image

Save your changes and view in the browser.  It appears we have some formatting issues; not totally surprising given how wide our initial design was.  It would be pretty unlikely that it would be perfect on the first try as well :).

image

Let’s see what adjustments can be made.

1. I removed the padding from the <ion-content> component and each of the <ion-item> components and <ion-avatar> component so the list takes up the full width of the screen and to reduce the spacing between items.
2. I moved the balance under the name and changed the date format.
3. I removed the label portion of the select and changed it to a placeholder.
4. I added some padding to the button to make sure it was a big enough area to tap.
5. I added some CSS to have the <ion-select> expand to 100% width as there will be no default content to start.

src/pages/invoice-list/invoice-list.html
image

src/pages/invoice-list/invoice-list.scss
image

image

And that looks much better… One last thing we need to add is the New Invoice button so a user can add an invoice right from this screen.  We’ll add it inside the content, but outside of the list.  This way it will always show as the user scrolls through the list of their invoices.  A search bar may be nice across the top as well, but we can add that at a later time.

image
Note: the 'secondary' directive should be 'color=“secondary”' for the New Invoice button.  You’ll notice below that the button remained blue, and not green.  We’ll address this error below.

image

We’ll create three more pages, one for the Invoice Detail, one for the Client List and one for the Client Detail.  We’ll then add all of the modules to the src/app/app.module.ts file and hook up the navigation buttons where appropriate.  At this point, the only thing we can’t access is the Client List, which we’ll be creating below, so let’s add a button next to the New Invoice button until we can implement a slide out nav and link it to the Client List when implemented.

It may be good to stop here, then come back when you have the other screens setup to get some practice creating pages and using the documentation to build out the other screens.  As you work through these and view the output, it is helpful to leave the developer tools open (F12 in Chrome) to view any errors that may come through in the console.

ionic g page invoiceDetail
ionic g page clientList
ionic g page clientDetail

image

src/app/app.module.ts
image

Invoice Detail

Add Edit Invoice[2]

src/pages/invoice-list/invoice-list.html
image
image

src/pages/invoice-list/invoice-list.ts
image

src/pages/invoice-detail/invoice-detail.html
image
image

src/pages/invoice-detail/invoice-detail.scss
image

image

Client List

Client List[2]

src/pages/invoice-list/invoice-list.html – Adding the Client List button
image

src/pages/invoice-list/invoice-list.ts – Navigating to the new client-list page component
image

src/pages/client-list/client-list.html
image

src/pages/client-list/client-list.ts
image

image

Client Detail

Add Edit Client[2]

src/pages/client-detail/client-detail.html
image

src/pages/client-list/client-list.scss – make the notes box a little taller than 2 rows.
image

image

Wow, we have made quite some progress through this post.  Play around with the application to make sure everything is working as expected.  If you are running into difficulties with anything, refer to the docs and be sure to have your console window open to watch for any errors.  Depending on the extent of your changes, occasionally terminating the application and restarting it resolves issues.

The application to this point doesn’t actually add, delete, or save any data, but we are getting there.

Next up, we’ll start creating our client side ViewModels for each of our pages, connect them to the user interface, and pass data between the pages.