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
Second, we will remove their declaration in the app module:
1
2
3
4
5
6
7
8
9
10
11
14
15
16
17
18
19
import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } 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: [{provide: ErrorHandler, useClass: IonicErrorHandler}]
And finally, will their usage in that tabs component:
1
2
3
4
5
6
7
8
9
import { Component } from '@angular/core';
@Component({
templateUrl: 'tabs.html'
})
export class TabsPage {
constructor() {
}
Now we're gonna define 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:
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>
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 would be implementing the chats
tab; First let's start by adding moment
as a dependency - a utility library in JavaScript which will help us parse, validate, manipulate, and display dates:
$ npm install --save moment
We will start by implementing a view stub, just so we can get the idea of Ionic's components:
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:
To display a view, we will need to have a component as well, whose basic structure should look like so:
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 '@angular/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 a define dummy chats array just so we can test our view. As you can see we're using the moment
package to fabricate date values. The Observable.of
syntax is used to create an Observable with a single value.
Now let's load the newly created component in the module definition so our Angular 2 app will recognize it:
2
3
4
5
6
7
8
9
10
11
12
13
16
17
18
19
20
21
22
import { IonicApp, IonicModule, IonicErrorHandler } 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: [{provide: ErrorHandler, useClass: IonicErrorHandler}]
Let's define it on the tabs component:
1
2
3
4
5
6
7
8
9
10
11
12
import { Component } from '@angular/core';
import { ChatsPage } from "../chats/chats";
@Component({
templateUrl: 'tabs.html'
})
export class TabsPage {
chatsTab = ChatsPage;
constructor() {
}
And define it as the root tab, which means that once we enter the tabs view, this is the initial tab which is gonna show up:
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>
Now, because we use TypeScript, we can define our own data-types and use then in our app, which will give you a better auto-complete and developing experience in most IDEs. In our application, we have 2 models at the moment: a chat
model and a message
model. We will define their interfaces in a file located under the path /modes/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;
}
}
The d.ts
extension stands for declaration - TypeScipt
, which basically tells the compiler that this is a declaration file, just like C++'s header files. In addition, we will need to add a reference to our models declaration file, so the compiler will recognize it:
11
12
13
14
15
16
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 we can finally use the chat model in the ChatsPage
:
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 '@angular/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 2 provides us with a comfortable theming system which is based on SASS variables. The theme definition file is located in src/theme/variable.scss
. Since we want our app to have a "Whatsappish" look, we will define a new SASS variable called whatsapp
in the variables file:
28
29
30
31
32
33
34
35
secondary: #32db64,
danger: #f53d3d,
light: #f4f4f4,
dark: #222,
whatsapp: #075E54
);
The whatsapp
color can be used by adding an attribute called color
with a value whatsapp
to any Ionic component:
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>
Now let's implement the chats view properly in the ChatsView
by showing all available chat items:
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 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. A chat item includes an image, the receiver's name, and its recent message.
The
async
pipe is used to iterate through data which should be fetched asynchronously, in this case, observables
Now, in order to finish our theming and styling, let's create a stylesheet file for our component:
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;
}
}
}
Ionic will load newly defined stylesheet files automatically, so you shouldn't be worried for importations.
Since Ionic 2 uses Angular 2 as the layer view, we can load Angular 2 modules just like any other plain Angular 2 application. One module that may come in our interest would be the angular2-moment
module, which will provide us with the ability to use moment
's utility functions in the view using pipes.
It requires us to install angular2-moment
module using the following command:
$ npm install --save angular2-moment
Now we will need to declare this module in the app's main component:
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: [
Which will make moment
available as a pack of pipes, as mentioned earlier:
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 provides us with components which can handle mobile events like: slide, tap and pinch. In the chats
view we're going to take advantage of the slide event, by implementing sliding buttons using the ion-item-sliding
component:
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<ion-content 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>
This implementation will reveal a remove
button once we slide a chat item to the left. The only thing left to do now would be implementing the chat removal event handler in the chats component:
62
63
64
65
66
67
68
69
70
71
72
73
}
]);
}
removeChat(chat: Chat): void {
this.chats = this.chats.map<Chat[]>(chatsArray => {
const chatIndex = chatsArray.indexOf(chat);
chatsArray.splice(chatIndex, 1);
return chatsArray;
});
}
}
{{{nav_step prev_ref="https://angular-meteor.com/tutorials/whatsapp2/ionic/1.0.0/setup" next_ref="https://angular-meteor.com/tutorials/whatsapp2/ionic/1.0.0/meteor-server-side"}}}