Chats page

Note: If you skipped ahead to this section, click here to download a zip of the tutorial at this point.

First Ionic Component

Now that we're finished with the initial setup, we can start building our app.

An application created by Ionic's CLI will have a very clear methodology. The app is made out of pages, each page is made out of 3 files:

  • .html - A view template file written in HTML based on Angular2's new template engine.
  • .scss - A stylesheet file written in a CSS pre-process language called SASS.
  • .ts - A script file written in Typescript.

By default, the application will be created with 3 pages - about, home and contact. Since our app's flow doesn't contain any of them, we first gonna clean them up by running the following commands:

$ rm -rf src/pages/about
$ rm -rf src/pages/home
$ rm -rf src/pages/contact

And we need to remove their usage from the tabs container:

2.2 Removed tabs from the Component src/pages/tabs/tabs.ts
1
2
3
4
5
6
7
8
9
import { Component } from [email protected]/core';
 
@Component({
  templateUrl: 'tabs.html'
})
export class TabsPage {
  constructor() {
 
  }

And remove them from the module definition:

2.1 Remove irrelevant pages src/app/app.module.ts
1
2
3
4
5
6
7
8
9
10
11
 
14
15
16
17
18
19
import { NgModule } from [email protected]/core';
import { IonicApp, IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { TabsPage } from '../pages/tabs/tabs';
 
@NgModule({
  declarations: [
    MyApp,
    TabsPage
  ],
  imports: [
...some lines skipped...
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    TabsPage
  ],
  providers: []

And let's change the actual tabs to show the tabs we want:

2.3 Edit tabs template to contain the necessary tabs src/pages/tabs/tabs.html
1
2
3
4
5
6
<ion-tabs>
  <ion-tab tabIcon="chatboxes"></ion-tab>
  <ion-tab tabIcon="contacts"></ion-tab>
  <ion-tab tabIcon="star"></ion-tab>
  <ion-tab tabIcon="clock"></ion-tab>
</ion-tabs>

We defined 4 tabs: chats, contacts, favorites and recents. In this tutorial we want to focus only on the messaging system, therefore we only gonna implement the chats tab, the rest is just for the layout.

If you will take a closer look at the view template we've just defined, you can see that one of the tab's attributes is wrapped with [square brackets]. This is part of Angular2's new template syntax and what it means is that the property called root of the HTML element is bound to the chatsTabRoot property of the component.

Our next step is to implement the chats tab - first let's start by adding moment as a dependency - we will use it soon:

$ npm install --save moment

Let's go ahead and implement the chats component, let's start with it's view (just a stub, we will later implement):

2.5 Added ChatsPage view src/pages/chats/chats.html
1
2
3
4
5
6
7
8
9
10
11
<ion-header>
  <ion-navbar>
    <ion-title>
      Chats
    </ion-title>
  </ion-navbar>
</ion-header>
 
<ion-content padding>
  Hello!
</ion-content>

Once creating an Ionic page it's recommended to use the following layout:

  • <ion-header> - The header of the page. Will usually contain content that should be bounded to the top like navbar.
  • <ion-content> - The content of the page. Will usually contain it's actual content like text.
  • <ion-footer> - The footer of the page. Will usutally contain content that should be bounded to the bottom like toolbars.

And now we will implement the actual Component:

2.6 Added ChatsPage Component src/pages/chats/chats.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import * as moment from 'moment';
import { Component } from [email protected]/core';
import { Observable } from "rxjs";
 
@Component({
  templateUrl: 'chats.html'
})
export class ChatsPage {
  chats: Observable<any[]>;
 
  constructor() {
    this.chats = this.findChats();
  }
 
  private findChats(): Observable<any[]> {
    return Observable.of([
      {
        _id: '0',
        title: 'Ethan Gonzalez',
        picture: 'https://randomuser.me/api/portraits/thumb/men/1.jpg',
        lastMessage: {
          content: 'You on your way?',
          createdAt: moment().subtract(1, 'hours').toDate()
        }
      },
      {
        _id: '1',
        title: 'Bryan Wallace',
        picture: 'https://randomuser.me/api/portraits/thumb/lego/1.jpg',
        lastMessage: {
          content: 'Hey, it\'s me',
          createdAt: moment().subtract(2, 'hours').toDate()
        }
      },
      {
        _id: '2',
        title: 'Avery Stewart',
        picture: 'https://randomuser.me/api/portraits/thumb/women/1.jpg',
        lastMessage: {
          content: 'I should buy a boat',
          createdAt: moment().subtract(1, 'days').toDate()
        }
      },
      {
        _id: '3',
        title: 'Katie Peterson',
        picture: 'https://randomuser.me/api/portraits/thumb/women/2.jpg',
        lastMessage: {
          content: 'Look at my mukluks!',
          createdAt: moment().subtract(4, 'days').toDate()
        }
      },
      {
        _id: '4',
        title: 'Ray Edwards',
        picture: 'https://randomuser.me/api/portraits/thumb/men/2.jpg',
        lastMessage: {
          content: 'This is wicked good ice cream.',
          createdAt: moment().subtract(2, 'weeks').toDate()
        }
      }
    ]);
  }
}

The logic is simple.

Once the component is created we gonna define dummy chats just so we can test our view. As you can see we're using a package called Moment to fabricate some date data.

We use Observable.of which is a shortcut for creating an Observable with a single value.

Now let's add the new Component to the module definition so Angular 2 will know that the Component exists:

2.7 Added ChatsPage to the module definition src/app/app.module.ts
2
3
4
5
6
7
8
9
10
11
12
13
 
16
17
18
19
20
21
22
import { IonicApp, IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { TabsPage } from '../pages/tabs/tabs';
import { ChatsPage } from "../pages/chats/chats";
 
@NgModule({
  declarations: [
    MyApp,
    ChatsPage,
    TabsPage
  ],
  imports: [
...some lines skipped...
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    ChatsPage,
    TabsPage
  ],
  providers: []

And let's add it to the TabsPage which is the page that manages our tabs:

2.8 Added chats tab root src/pages/tabs/tabs.ts
1
2
3
4
5
6
7
8
9
10
11
12
import { Component } from [email protected]/core';
import { ChatsPage } from "../chats/chats";
 
@Component({
  templateUrl: 'tabs.html'
})
export class TabsPage {
  chatsTab = ChatsPage;
 
  constructor() {
 
  }

And add the tab definition to the view, so the tab we create will be linked to the new Component:

2.9 Added root to the tab element src/pages/tabs/tabs.html
1
2
3
4
5
<ion-tabs>
  <ion-tab [root]="chatsTab" tabIcon="chatboxes"></ion-tab>
  <ion-tab tabIcon="contacts"></ion-tab>
  <ion-tab tabIcon="star"></ion-tab>
  <ion-tab tabIcon="clock"></ion-tab>

TypeScript Interfaces

Now, because we use TypeScript, we can defined our types and use then in our app, and in most of the IDEs you will get a better auto-complete and developing experience.

So in our application, at the moment, we have two models: Chat and Message, so let's create the TypeScript definition for them.

The file extension should be .d.ts - this is the way to tell TypeScript that the file does not contain any login - only interfaces.

We will locate it under /models/ directory, and later we will see how we can share those model definitions in both server side and client side.

So let's create the definitions file:

2.10 Added chat and message models models/whatsapp-models.d.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
declare module 'api/models/whatsapp-models' {
  interface Chat {
    _id?: string;
    title?: string;
    picture?: string;
    lastMessage?: Message;
  }
 
  interface Message {
    _id?: string;
    chatId?: string;
    content?: string;
    createdAt?: Date;
  }
}

Note that we declared our interface inside a module called api/models/whatsapp-models - so we will be able to import the models from that path.

And we need to add this definition to our TypeScript config (tsconfig.json), so it would be available in our code:

2.11 Added models typings into the config src/declarations.d.ts
11
12
13
14
15
  For more info on type definition files, check out the Typescript docs here:
  https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html
*/
/// <reference path="../models/whatsapp-models.d.ts" />
declare module '*';

Now, let's use our new model in the ChatsPage:

2.12 Use Chat model in ChatsPage src/pages/chats/chats.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import * as moment from 'moment';
import { Component } from [email protected]/core';
import { Observable } from "rxjs";
import { Chat } from "api/models/whatsapp-models";
 
@Component({
  templateUrl: 'chats.html'
})
export class ChatsPage {
  chats: Observable<Chat[]>;
 
  constructor() {
    this.chats = this.findChats();
  }
 
  private findChats(): Observable<Chat[]> {
    return Observable.of([
      {
        _id: '0',

Ionic Themes

Ionic provides use a theme engine in order to define style faster and more efficient.

The theme definition file is located in src/theme/variable.scss, and at the moment we will just add a new theme color, called whatsapp:

2.13 Added theme color definition src/theme/variables.scss
27
28
29
30
31
32
33
34
  secondary:  #32db64,
  danger:     #f53d3d,
  light:      #f4f4f4,
  dark:       #222,
  whatsapp:   #075E54
);
 
 

And now we will be able to use the new color anywhere in any Ionic Component by adding color="whatsapp" to the Component.

2.14 Apply whatsapp theme on tabs view src/pages/tabs/tabs.html
1
2
3
4
<ion-tabs color="whatsapp">
  <ion-tab [root]="chatsTab" tabIcon="chatboxes"></ion-tab>
  <ion-tab tabIcon="contacts"></ion-tab>
  <ion-tab tabIcon="star"></ion-tab>

So let's add it to the view of the ChatsPage, and we will also use some more Ionic Components along with Angular 2 features:

2.15 Added chats page view with a list of chats src/pages/chats/chats.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<ion-header>
  <ion-navbar color="whatsapp">
    <ion-title>
      Chats
    </ion-title>
    <ion-buttons end>
      <button ion-button icon-only class="add-chat-button">
        <ion-icon name="person-add"></ion-icon>
      </button>
      <button ion-button icon-only class="options-button">
        <ion-icon name="more"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>
 
<ion-content padding class="chats-page-content">
  <ion-list class="chats">
    <button ion-item *ngFor="let chat of chats | async" class="chat">
      <img class="chat-picture" [src]="chat.picture">
 
      <div class="chat-info">
        <h2 class="chat-title">{{chat.title}}</h2>
 
        <span *ngIf="chat.lastMessage" class="last-message">
          <p class="last-message-content">{{chat.lastMessage.content}}</p>
          <span class="last-message-timestamp">{{chat.lastMessage.createdAt}}</span>
        </span>
      </div>
    </button>
  </ion-list>
</ion-content>

We use ion-list which Ionic translate into a list, and use ion-item for each one of the items in the list, and we also added to the view some images and text for each chat item.

We use ngFor along with the async Pipe because we will use RxJS and Observables in the tutorial!

Now, in order to finish our theming and styling, let's create a stylesheet file for our Component:

2.16 Added some styles src/pages/chats/chats.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.chats-page-content {
  .chat-picture {
    border-radius: 50%;
    width: 50px;
    float: left;
  }
 
  .chat-info {
    float: left;
    margin: 10px 0 0 20px;
 
    .last-message-timestamp {
      position: absolute;
      top: 10px;
      right: 10px;
      font-size: 14px;
      color: #9A9898;
    }
  }
}

In Ionic 2, there is no need to load each specific style file - Ionic loads any style file under the app folder.

External Angular 2 Modules

Ionic 2 application works just like any other Angular 2 application, which means we can use any external packages that we need.

For example, we will add a usage with angular2-moment package, that adds useful Pipes we can use in our view, in order to manipulate the display of Date variables.

So let's add this package first:

$ npm install --save angular2-moment

Now we need to tell our Angular 2 application to load that external module, so it would be available for use:

2.18 Import MomentModule into the module definition src/app/app.module.ts
3
4
5
6
7
8
9
 
12
13
14
15
16
17
18
19
import { MyApp } from './app.component';
import { TabsPage } from '../pages/tabs/tabs';
import { ChatsPage } from "../pages/chats/chats";
import { MomentModule } from "angular2-moment";
 
@NgModule({
  declarations: [
...some lines skipped...
    TabsPage
  ],
  imports: [
    IonicModule.forRoot(MyApp),
    MomentModule
  ],
  bootstrap: [IonicApp],
  entryComponents: [

And let's use a Pipe from that package it in the view:

2.19 Apply calendar pipe to chats view template src/pages/chats/chats.html
24
25
26
27
28
29
30
 
        <span *ngIf="chat.lastMessage" class="last-message">
          <p class="last-message-content">{{chat.lastMessage.content}}</p>
          <span class="last-message-timestamp">{{chat.lastMessage.createdAt | amCalendar }}</span>
        </span>
      </div>
    </button>

Ionic Touch Events

Ionic provides us special Component's which handles touch events, for example: slide, tap and pinch.

We can use those in our view, let's add a sliding button that will show us more functionality for each chat.

We add a remove button for each chat, so let's do it:

2.20 Add chat removal button to view template src/pages/chats/chats.html
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 
<ion-content padding class="chats-page-content">
  <ion-list class="chats">
    <ion-item-sliding *ngFor="let chat of chats | async">
      <button ion-item class="chat">
        <img class="chat-picture" [src]="chat.picture">
 
        <div class="chat-info">
          <h2 class="chat-title">{{chat.title}}</h2>
 
          <span *ngIf="chat.lastMessage" class="last-message">
            <p class="last-message-content">{{chat.lastMessage.content}}</p>
            <span class="last-message-timestamp">{{chat.lastMessage.createdAt | amCalendar }}</span>
          </span>
        </div>
      </button>
      <ion-item-options class="chat-options">
        <button ion-button color="danger" class="option option-remove" (click)="removeChat(chat)">Remove</button>
      </ion-item-options>
    </ion-item-sliding>
  </ion-list>
</ion-content>

And bind the event handler to the Component (we will implement the remove feature later):

2.21 Add chat removal stub method to chats component src/pages/chats/chats.ts
62
63
64
65
66
67
68
69
      }
    ]);
  }
 
  removeChat(chat: Chat): void {
    // TODO: Implement it later
  }
}