Now that we're finished with the initial setup, we can start building our app.
An Ionic application is made out of pages, each page is an Angular component.
Let's create the first page and call it TabsContainer
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import {Component} from "@angular/core";
@Component({
selector: "tabs-container",
template: `
<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>
`
})
export class TabsContainerComponent {
constructor() {
}
}
We defined 3 tabs (see documentation): chats
, contacts
, favorites
.
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.
Now we need to include this component in the AppModule
to make it available for our application:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { IonicApp, IonicModule } from "ionic-angular";
import {TabsContainerComponent} from "../pages/tabs-container/tabs-container.component";
@NgModule({
// Components, Pipes, Directive
declarations: [
AppComponent,
TabsContainerComponent
],
// Entry Components
entryComponents: [
AppComponent,
TabsContainerComponent
],
// Providers
providers: [
One thing is missing and that's the root page. The application doesn't know which page to load at the beginning.
Navigation is handled through the <ion-nav>
component. Go to AppComponent's template to change it:
1
<ion-nav [root]="rootPage"></ion-nav>
Now we can define rootPage
and use TabsContainerComponent
:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Platform } from "ionic-angular";
import { StatusBar } from "ionic-native";
import template from './app.component.html';
import {TabsContainerComponent} from "../pages/tabs-container/tabs-container.component";
@Component({
selector: 'app',
template
})
export class AppComponent {
rootPage = TabsContainerComponent;
constructor(platform: Platform) {
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
Navigation in Ionic works as a simple stack. New pages are pushed onto and popped off of, corresponding to moving forward and backward in history.
We're going to create a component that contains list of chats.
First thing, a template:
1
2
3
4
5
6
7
8
9
<ion-header>
<ion-navbar>
<ion-title>Chats</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<h2>Welcome!</h2>
</ion-content>
Then, the actual component, called ChatsComponent
:
1
2
3
4
5
6
7
8
9
10
11
12
import {Component} from "@angular/core";
import template from "./chats.component.html"
@Component({
selector: "chats",
template
})
export class ChatsComponent {
constructor() {
}
}
As you probably remember, it still need to be added to AppModule:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { AppComponent } from './app.component';
import { IonicApp, IonicModule } from "ionic-angular";
import {TabsContainerComponent} from "../pages/tabs-container/tabs-container.component";
import {ChatsComponent} from "../pages/chats/chats.component";
@NgModule({
// Components, Pipes, Directive
declarations: [
AppComponent,
TabsContainerComponent,
ChatsComponent
],
// Entry Components
entryComponents: [
AppComponent,
TabsContainerComponent,
ChatsComponent
],
// Providers
providers: [
Since, the component is available, we can bind it to Chats tab:
1
2
3
4
5
6
7
8
9
10
11
13
14
15
16
17
18
19
20
import {Component} from "@angular/core";
import {ChatsComponent} from "../chats/chats.component";
@Component({
selector: "tabs-container",
template: `
<ion-tabs>
<ion-tab [root]="chatsRoot" tabIcon="chatboxes"></ion-tab>
<ion-tab tabIcon="contacts"></ion-tab>
<ion-tab tabIcon="star"></ion-tab>
<ion-tab tabIcon="clock"></ion-tab>
...some lines skipped...
`
})
export class TabsContainerComponent {
chatsRoot = ChatsComponent;
constructor() {
}
Ionic2 provides us with a new theming system.
The theme is determined thanks to SASS variables located in the file client/styles/ionic.scss
.
By changing these variables our entire app's theme will be changed as well.
Not only that, but you can also add new theming colors, and they should be available on the HTML as attributes, and the should affect the theming of most Ionic elements once we use them.
Since we want our app to have a Whatsapp theme, we gonna define a new variable called whatsapp
:
27
28
29
30
31
32
33
34
danger: #f53d3d,
light: #f4f4f4,
dark: #222,
favorite: #69BB7B,
whatsapp: #075E54
);
// Components
Now whenever we will use it as an HTML attribute we gonna have a greenish background, just like Whatsapp.
4
5
6
7
8
9
10
@Component({
selector: "tabs-container",
template: `
<ion-tabs color="whatsapp">
<ion-tab [root]="chatsRoot" tabIcon="chatboxes"></ion-tab>
<ion-tab tabIcon="contacts"></ion-tab>
<ion-tab tabIcon="star"></ion-tab>
It's time to think about the data structure of chats and messages.
Let's begin with a message. It should contain content and date of creating.
1
2
3
4
5
export interface Message {
_id?: string;
content?: string;
createdAt?: Date;
}
Because it represents a Mongo Object we also added _id
property.
Do the same for a chat:
1
2
3
4
5
6
7
8
import {Message} from "./message.model";
export interface Chat {
_id?: string;
title?: string;
picture?: string;
lastMessage?: Message;
}
Chat has title, picture and an object with a last message.
Whatsapp needs data, so we going to define dummy chats just so we can test our view.
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
import {Component} from "@angular/core";
import template from "./chats.component.html"
import {Observable} from "rxjs";
import {Chat} from "../../../../both/models/chat.model";
import * as moment from "moment";
@Component({
selector: "chats",
template
})
export class ChatsComponent {
chats: Observable<Chat[]>;
constructor() {
this.chats = 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()
}
}
])
}
}
As you can see we're using a package called Moment
to fabricate some dates. Let's install it:
$ npm install moment
It requires declarations:
$ typings install --save --global dt~moment
We used Observable.of
that creates an Observable
that emits values we specified as arguments.
Let's update the view of ChatsComponent:
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
<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 placed two buttons at the end of Navigation Bar. First's purpose is to add new chat, but second's to open a menu with more options.
New ion-content
contains list of chats. Each element has a picture, title and an information about the last message.
NOTE: Ionic elements will always have a prefix of
ion
and are self explanatory. Further information about Ionic's HTML elements can be found here. It's very important to use these elemnts since they are the ones who provides us with the mobile-app look.
The *ngFor
attribute is used for iteration and is equivalent to Angular1's ng-for
attribute. The '*' sign just tells us that this is a template directive we're dealing with (A directive that should eventually be rendered in the view).
As you probably noticed, we used AsyncPipe
to display the result of Observable under chat
property.
Let' make it to look better by creating the chats.component.scss
file:
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;
}
}
}
To include those styles in our component we need to:
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import {Observable} from "rxjs";
import {Chat} from "../../../../both/models/chat.model";
import * as moment from "moment";
import style from "./chats.component.scss";
@Component({
selector: "chats",
template,
styles: [
style
]
})
export class ChatsComponent {
chats: Observable<Chat[]>;
We also want to display date under createdAt
property in a proper way. Moment library contains a package for Angular that will help us.
$ npm install [email protected] --save
It's not yet available to Whatsapp. Let's change it:
3
4
5
6
7
8
9
24
25
26
27
28
29
30
31
import { IonicApp, IonicModule } from "ionic-angular";
import {TabsContainerComponent} from "../pages/tabs-container/tabs-container.component";
import {ChatsComponent} from "../pages/chats/chats.component";
import {MomentModule} from "angular2-moment";
@NgModule({
// Components, Pipes, Directive
...some lines skipped...
],
// Modules
imports: [
IonicModule.forRoot(AppComponent),
MomentModule
],
// Main Component
bootstrap: [ IonicApp ]
Now we can use AmCalendarPipe
:
19
20
21
22
23
24
25
<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>
Pipes serves the same proposes as AngularJS' filters and they share exactly the same syntax, only they are called in a different name.
{{{nav_step prev_ref="https://angular-meteor.com/tutorials/whatsapp2/meteor/1.0.0/setup" next_ref="https://angular-meteor.com/tutorials/whatsapp2/meteor/1.0.0/meteor-server-side"}}}