A Master Detail Interface is a common pattern in computer systems where a master list is displayed, and when an item is selected, more details about that item are displayed in a separate view.

Master Detail
Master Detail

On this tutorial we’ll create all components for a clean master/detail implementation:

  • ./src/pages/
    • contacts
    • contact-details
  • ./src/models/contact.interface.ts
  • ./src/providers/contacts/
    • contacts.service.ts
    • contacts.service-mock.ts
    • mock-contacts.ts

If you’re not familiar with Ionic 2 already, I’d recommend reading my previous tutorial Getting Started with Ionic to get up and running.

Generate pages posts-list and post-details

We’ll use ionic generate on this tutorial to create required components.

$ ionic g page contacts --no-modules
$ ionic g page contact-details

Generate data provider and mockup

$ ionic g provider contacts
$ cp src/providers/contacts/contacts.ts src/providers/contacts/contacts.service-mock.ts
$ mv src/providers/contacts/contacts.ts src/providers/contacts/contacts.service.ts

Ionic provider generator automatically add ContactsProvider on app.module.ts. So, after renaming the files, we should update the path.

Using Angular 2 barrels

To allow switching of service (mock vs regular) easily we’ll use angular barrels. We create an src/providers/contacts/index.ts file where we reference the current Service to use:

//export * from './contacts.service';
export * from './contacts.service-mock';

And we replace on app.module.ts and pages/contacts/contacts.ts the import import { ContactsProvider } from '../providers/contacts/contacts.service-mock' to import { ContactsProvider } from '../providers/contacts/';

Mock Content

src/providers/contacts/mock-contacts.ts

let contacts = [
    {
        id: "6",
        firstName: "Miriam",
        lastName: "Aupont",
        title: "Senior Broker",
        landlinePhone: "617-244-3672",
        mobilePhone: "617-244-3672",
        email: "miriam@ionicrealty.com",
        picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/miriam_aupont.jpg"
    },
    {
        id: "7",
        firstName: "Michelle",
        lastName: "Lambert",
        title: "Senior Broker",
        landlinePhone: "617-244-3672",
        mobilePhone: "617-244-3672",
        email: "michelle@ionicrealty.com",
        picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/michelle_lambert.jpg"
    },
    {
        id: "8",
        firstName: "Victor",
        lastName: "Ochoa",
        title: "Senior Broker",
        landlinePhone: "617-244-3672",
        mobilePhone: "617-244-3672",
        email: "victor@ionicrealty.com",
        picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/victor_ochoa.jpg"
    }
];

export default contacts;

Stop using observable, just use promise

We’ll consider on this tutorial the use case we just need to GET data from the server and display the data. When you have a single event, just use promise! I recommend the reading of Angular — Stop using observable when you should use a promise to understand this topic.

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class contactsProvider {

    findAll() {
        return this.http.get("https://jsonplaceholder.typicode.com/posts")
          .map(res => res.json())
          .toPromise();
    }

    findById(id) {
        return this.http.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
          .map(res => res.json())
          .toPromise();
    }
}

If you are interested in comparing both strategies Observable vs Promise I recommend the reading of following post Fetching Data In Angular2.

Import HttpModule

As we use http on our provider we have to import HttpModule on app.module.ts

import { HttpModule } from '@angular/http';
...
@NgModule({
  declarations: [
...
  ],
  imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp)
  ],
  ...

Create model contact

We can use the ionic generator but it will create a folder for each model and doesn’t seem useful, then I prefer to create manually src/models/contact.interface.ts

export interface Contact {
  
  picture: string,
  firstName: string,
  lastName: string,
  title: string,
  landline: string,
  mobilePhone: string,
  email: string
}

Views

Contacts

See below an example of view to render list of contacts inspired by DreamHouse Project of Christophe Coenraets.

  <ion-list>
    <button ion-item *ngFor="let contact of contacts" (click)="pushDetailsPage('contact/details/', contact.id)">
      <ion-avatar item-left *ngIf="contact.picture; else anonymous">
          <img src=""/>
      </ion-avatar>
      <ng-template #anonymous>
          <ion-avatar item-left>
              <ion-icon style="font-size: 3.0em" name="contact"></ion-icon>
          </ion-avatar>
      </ng-template>
      
      <h2> </h2>
      <p></p>
    </button>
  </ion-list>

Contact details

src/pages/contact-details/contact-details.ts

<ion-content class="contact">

  <ion-card>

    <ion-card-content>
      <img src="" />
      <h2> </h2>
      <h3></h3>
    </ion-card-content>

    <ion-list>
      <a href="tel:" ion-item>
        <ion-icon name="call" item-left></ion-icon>
        <p>Call Office</p>
        <h2></h2>
      </a>
      <a href="tel:" ion-item>
        <ion-icon name="call" item-left></ion-icon>
        <p>Call Mobile</p>
        <h2></h2>
      </a>
      <a [href]="'sms:' + contact.mobilePhone" ion-item>
        <ion-icon name="text" item-left></ion-icon>
        <p>Text</p>
        <h2></h2>
      </a>
      <a href="mailto:" ion-item>
        <ion-icon name="mail" item-left></ion-icon>
        <p>Email</p>
        <h2></h2>
      </a>
    </ion-list>

  </ion-card>

</ion-content>

src/pages/contact-details/contact-details.scss

page-contact-details {
  .contact {
    ion-card {
      margin-top: 100px;
      overflow: visible;
      ion-card-content {
        background-color: map-get($colors, primary);
        color: #424242;
        text-align: center;
        padding-bottom: 28px;
        img {
          height: 160px;
          width: 160px;
          border-radius: 50%;
          margin-top: -100px;
          border: solid 4px #FFFFFF;
          display: inline;
        }
        h2 {
          font-size: 2.5rem;
          margin-top: .5rem;
        }
        h3 {
          font-size: 1.8rem;
        }
      }
    }
  }
}

Furthermore


Victor Dias

Sharing mobile Experiences

Follow me