Use List, Nested Detail and Form in Angular2-Ionic2 to Create a Mobile App

When you create a mobile application, you can look at the design of the application essentially as consisting of the following abstract parts:

  1. a main module for application configuration,
  2. a navigation component to manage the main navigation stack,
  3. for each object in the application:
  • a list component with
  • a detail component, and

4. a form component for the detail.

The detail component contains a form element with CRUD permissions to Create, Read, Update (Edit) and Delete a detail item. The form component is included in the detail component by a custom HTML tag.

By following such a standard design pattern for mobile apps, you can quickly and easily create a mobile application.

This tutorial will use Ionic2 with Angular2 and TypeScript, to create a mobile application from scratch in the following steps:

  1. Create new Ionic2 application with ‘tabs’ template
  2. Create a json file with Contact data
  3. Create a DataService to read Contact data from file
  4. Add a Contact form component
  5. Add a TypeScript class for Contact
  6. Add a Contact detail page
  7. Edit a Contact list page
  8. Configure App module and Contact detail

1. Create new Ionic2 application with ‘tabs’ template

Open a command-line and change to your development folder. Create a new Ionic app ‘myApp’ using the tabs template, change the active folder to your new project folder, and test the new app.

$ ionic start myApp tabs
$ cd ./myApp
$ ionic serve

In Google Chrome browser, you can view the application in mobile view:

  • In the top right, click the ‘Customize and control Google Chrome’ button,
  • Click ‘More Tools…’ > ‘Developer Tools’,
  • Click the ‘Toggle device toolbar’ to switch to mobile view
toggle device toolbar between web and mobile
  • You can adjust the window size by clicking the ‘Response’ dropdown
change default mobile emulator

In the new app, click the Contact tab to see a list of currently a list with a single contact.

Ionic2 tabs application with contacts

2. Create a json file with Contact data

Add the project folder to your favorite text IDE, so you can easily edit the rest of the application.

I want to add some test data to test the application. Because I am developing a test application on localhost, I want to keep the application as simple as possible and simply read the data from a local file.

Add a new folder ‘~/src/assets/data/’ and in the data folder, create a new file ‘contacts.json’. Edit the file ‘contacts.json’.

{
"contacts" : [
{
"firstname" : "User1",
"lastname" : "One",
"username" : "user1one",
"city" : "New York",
"state" : "NY",
"country" : "United States of America"
},
{
"firstname" : "User2",
"lastname" : "Two",
"username" : "user2two",
"city" : "Dallas",
"state" : "TX",
"country" : "United States of America"
},
{
"firstname" : "User3",
"lastname" : "Three",
"username" : "user3three",
"city" : "Chicago",
"state" : "IL",
"country" : "United States of America"
}
]
}

Make sure you use double quotes (“) as delimiter.

3. Create a DataService to read Contact data from file

In order to separate functionality and create a uniform data access method, I will create a designated data provider class.

$ ionic generate provider DataService

This creates a new provider ‘data-service/data-service.ts’ in the ‘~/src/providers/’ folder.

Edit the file ‘data-service.ts’.

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';
@Injectable()
export class DataServiceProvider {
public contactsFilePath: string = './assets/data/contacts.json';
constructor(public http: Http) { }
public getContacts(): Promise<Object> {
return this.http.get(this.contactsFilePath)
.toPromise()
.then((response) => {
return response.json();
})
.catch((err) => {
console.log(err);
});
}
}

4. Add a Contact form component

I want to create a separate form component for each object in the application. I want to isolate the form view component as much as possible, and this way I can keep all the different detail pages as similar and readable as possible. The form component is then included in the detail page by a custom HTML-tag for the form of that particular object. This makes the form component a nested form detail page in the Contact detail page.

To generate the form component run the following command in the cli.

$ ionic generate component contact-detail-form

This generates several files in the directory ‘~/src/components/contact-detail-form’ and a components module ‘~/src/components/components.module.ts’.

Edit the file ‘contact-detail-form.html’.

<div>
<form>
<ion-list>
<ion-item>
<ion-label for="firstname" stacked>firstname</ion-label>
<ion-input type="text" name="firstname" [(ngModel)]="contact.firstname" [disabled]="!edit"></ion-input>
</ion-item>
<ion-item>
<ion-label for="lastname" stacked>lastname</ion-label>
<ion-input type="text" name="lastname" [(ngModel)]="contact.lastname" [disabled]="!edit"></ion-input>
</ion-item>
<ion-item>
<ion-label for="username" stacked>username</ion-label>
<ion-input type="text" name="username" [(ngModel)]="contact.username" [disabled]="!edit"></ion-input>
</ion-item>
<ion-item>
<ion-label for="city" stacked>city</ion-label>
<ion-input type="text" name="city" [(ngModel)]="contact.city" [disabled]="!edit"></ion-input>
</ion-item>
<ion-item>
<ion-label for="state" stacked>state</ion-label>
<ion-input type="text" name="state" [(ngModel)]="contact.state" [disabled]="!edit"></ion-input>
</ion-item>
<ion-item>
<ion-label for="country" stacked>country</ion-label>
<ion-input type="text" name="country" [(ngModel)]="contact.country" [disabled]="!edit"></ion-input>
</ion-item>
</ion-list>
<div padding>
<button ion-button color="primary" (click)="onClicked(!edit)">{{edit? "save" : "edit"}}</button>
</div>
</form>
</div>

Edit the ‘contact-detail-form.ts’ component to handle the edit-save button click event.

import { Component } from '@angular/core';
@Component({
selector: 'contact-detail-form',
templateUrl: 'contact-detail-form.html'
})
export class ContactDetailFormComponent {
edit: boolean = null;
constructor() {
this.edit = false;
}
onClicked(toggle){
if(this.edit==true){
}
this.edit = toggle;
}
onSubmit(formValue: any){
console.log(formValue);
}
}

5. Add a TypeScript class for Contact

I want to create a custom TypeScript class for Contact. I will use the Contact class to load the Contact data from the data file in the DataService, and quickly access the class properties in the form. Using a TypeScript class makes this simpler and quicker.

In the folder ‘~/src/components/’ create a new file ‘contact-model.ts’.

Edit the file ‘contact-model.ts’.

export class ContactModel {
constructor(
public id: number,
public firstname: string,
public lastname: string,
public username: string,
public city: string,
public state: string,
public country: string
){}
}

Edit the file ‘~/src/components/contact-detail-form.ts’ again to add the @Input decorator that adds metadata to the class and makes the ‘contact’ property available for binding. See Angular Directive.

import { Component, Input } from '@angular/core';
import { ContactModel } from '../contact-model';
@Component({
selector: 'contact-detail-form',
templateUrl: 'contact-detail-form.html'
})
export class ContactDetailFormComponent {
@Input() contact: ContactModel = null;
edit: boolean = null;
constructor() {
this.edit = false;
}
onClicked(toggle){
if(this.edit==true){
}
this.edit = toggle;
}
onSubmit(formValue: any){
console.log(formValue);
}
}

The @Input decorator tells Angular that the ‘contact’ property is public and available for binding by the parent component ‘contact-detail.html’ (added next). See also Why add @Input. In ‘contact-detail.html’ you will add the binding, the ‘selectedContact’ is selected and passed to the detail page by the list page.

<contact-detail-form [contact]="selectedContact"></contact-detail-form>

6. Add a Contact detail page

Now you add the detail page for Contact. When a user clicks the contacts tab, and clicks a single contact item from the contact list, the application will navigate to the detail page for that contact.

$ ionic generate page contact-detail

Edit the file ‘~/src/pages/contact-detail/contact-detail.html’.

<ion-header>
<ion-navbar>
<ion-title>contact-detail</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<contact-detail-form [contact]="selectedContact"></contact-detail-form>
</ion-content>

Edit the file ‘src/pages/contact-detail/contact-detail.ts’.

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { ContactModel } from '../../components/contact-model';
@IonicPage()
@Component({
selector: 'page-contact-detail',
templateUrl: 'contact-detail.html',
})
export class ContactDetailPage {
selectedContact: ContactModel = null;
constructor(public navCtrl: NavController,
public navParams: NavParams) {
this.selectedContact = navParams.get("contact");
}
ionViewDidLoad() {
console.log('ionViewDidLoad ContactDetailPage');
}
}

7. Edit the Contact list page

Next, we need to connect the list page to the detail page, so that when you click an item in the Contact list, the app will take you to the Contact detail page. You add an ‘onSelect’ method to be called when the click-event is triggered, and when this happens the ‘selectedContact’ object is set to the current ‘contact’ that was clicked.

Edit the file ‘src/pages/contact/contact.html’.

<ion-header>
<ion-navbar>
<ion-title>
Contact
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item *ngFor="let contact of contacts.contacts"
(click)="onSelect(contact)"
[class.selected]="contact === selectedContact">
<h3>{{ contact.firstname }} {{ contact.lastname }}</h3>
<p>{{ contact.city }}, {{ contact.state }}</p>
</ion-item>
</ion-list>
</ion-content>

To populate the list of Contacts, we call the DataService provider, which has read the contacts from the ‘contacts.json’ file, in the constructor. We implement the ‘onSelect’ method that is triggered by the click-event. The ‘onSelect’ method pushes the ContactDetailPage onto the navigation stack and passes the selected contact as a parameter to the ContactDetailPage.

Edit the file ‘src/pages/contact/contact.ts’.

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { DataServiceProvider } from '../../providers/data-service/data-service';
import { ContactDetailPage } from '../contact-detail/contact-detail';
@Component({
selector: 'page-contact',
templateUrl: 'contact.html'
})
export class ContactPage {
contacts: any = null;
constructor(public navCtrl: NavController,
public dataService: DataServiceProvider) {
this.contacts = this.dataService.getContacts()
.then( (contacts) => {
this.contacts = contacts;
},
(error) => {
console.log("error: "+ error);
});
}
onSelect(item) {
this.navCtrl.push(ContactDetailPage, { contact: item });
}
}

8. Configure App module and Contact detail

There are a few loose ends we need to tie up to connect the different parts together by configuring the ‘app.module.ts’ correctly.

Edit the file ‘/src/app/app.module.ts’. Add the ‘HttpModule’, ‘ContactDetailPage’, and ‘ContactDetailFormComponent’. Also, we need to import ‘CUSTOM_ELEMENTS_SCHEMA’ from ‘Angular/core’. The generate command configured the ‘DataServiceProvider’ already when we created the DataService provider.

import { NgModule, ErrorHandler, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { AboutPage } from '../pages/about/about';
import { ContactPage } from '../pages/contact/contact';
import { HomePage } from '../pages/home/home';
import { TabsPage } from '../pages/tabs/tabs';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { DataServiceProvider } from '../providers/data-service/data-service';
import { HttpModule } from '@angular/http';
import { ContactDetailPage } from '../pages/contact-detail/contact-detail';
import { ContactDetailFormComponent } from '../components/contact-detail-form/contact-detail-form';
@NgModule({
declarations: [
MyApp,
AboutPage,
ContactPage,
HomePage,
TabsPage,
ContactDetailPage,
ContactDetailFormComponent
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp),
HttpModule
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
AboutPage,
ContactPage,
HomePage,
TabsPage,
ContactDetailPage
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler},
DataServiceProvider
],
schemas:[CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}

Also, note the line ‘schemas:[CUSTOM_ELEMENTS_SCHEMA]’. You need to add this because we are using a custom HTML tag ‘<contact-detail-form>’ in the ‘ContactDetailPage.html’.

Run

Now try to run the application, click the Contacts tab, click on of the contacts in the list, and edit the contact.

$ ionic serve
contact detail