Fork me on GitHub

WhatsApp Clone with Ionic 2 and Meteor CLI

Socially Merge Version (Last Update: 14.02.2017)

Realtime Meteor Server

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

Now that we have the initial chats layout and its component, we will take it a step further by providing the chats data from a server instead of having it locally.

Collections

Collections in Meteor are actually references to MongoDB collections. This functionality is provided to us by a Meteor package called Minimongo, and it shares almost the same API as a native MongoDB collection. In this tutorial we will be wrapping our collections using RxJS's Observables, which is available for us thanks to meteor-rxjs.

Our initial collections are gonna be the chats and messages collections; One is going to store chats-models, and the other is going to store messages-models:

4.1 Add chats and messages collections imports/collections/chats.ts
1
2
3
4
import { MongoObservable } from 'meteor-rxjs';
import { Chat } from '../models';
 
export const Chats = new MongoObservable.Collection<Chat>('chats');
4.1 Add chats and messages collections imports/collections/messages.ts
1
2
3
4
import { MongoObservable } from 'meteor-rxjs';
import { Message } from '../models';
 
export const Messages = new MongoObservable.Collection<Message>('messages');

We chose to create a dedicated module for each collection, because in the near future there might be more logic added into each one of them. To make importation convenient, we will export all collections from a single file:

4.2 Added main export file imports/collections/index.ts
1
2
export * from './chats';
export * from './messages';

Now instead of requiring each collection individually, we can just require them from the index.ts file.

Data fixtures

Since we have real collections now, and not dummy ones, we will need to fill them up with some data in case they are empty, so we can test our application properly. Let's create our data fixtures in the server:

4.3 Move stubs data to the server side server/main.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
65
66
67
68
69
70
import { Meteor } from 'meteor/meteor';
import * as Moment from 'moment';
import { Chats, Messages } from '../imports/collections';
import { MessageType } from '../imports/models';
 
Meteor.startup(() => {
  if (Chats.find({}).cursor.count() === 0) {
    let chatId;
 
    chatId = Chats.collection.insert({
      title: 'Ethan Gonzalez',
      picture: 'https://randomuser.me/api/portraits/thumb/men/1.jpg'
    });
 
    Messages.collection.insert({
      chatId: chatId,
      content: 'You on your way?',
      createdAt: Moment().subtract(1, 'hours').toDate(),
      type: MessageType.TEXT
    });
 
    chatId = Chats.collection.insert({
      title: 'Bryan Wallace',
      picture: 'https://randomuser.me/api/portraits/thumb/lego/1.jpg'
    });
 
    Messages.collection.insert({
      chatId: chatId,
      content: 'Hey, it\'s me',
      createdAt: Moment().subtract(2, 'hours').toDate(),
      type: MessageType.TEXT
    });
 
    chatId = Chats.collection.insert({
      title: 'Avery Stewart',
      picture: 'https://randomuser.me/api/portraits/thumb/women/1.jpg'
    });
 
    Messages.collection.insert({
      chatId: chatId,
      content: 'I should buy a boat',
      createdAt: Moment().subtract(1, 'days').toDate(),
      type: MessageType.TEXT
    });
 
    chatId = Chats.collection.insert({
      title: 'Katie Peterson',
      picture: 'https://randomuser.me/api/portraits/thumb/women/2.jpg'
    });
 
    Messages.collection.insert({
      chatId: chatId,
      content: 'Look at my mukluks!',
      createdAt: Moment().subtract(4, 'days').toDate(),
      type: MessageType.TEXT
    });
 
    chatId = Chats.collection.insert({
      title: 'Ray Edwards',
      picture: 'https://randomuser.me/api/portraits/thumb/men/2.jpg'
    });
 
    Messages.collection.insert({
      chatId: chatId,
      content: 'This is wicked good ice cream.',
      createdAt: Moment().subtract(2, 'weeks').toDate(),
      type: MessageType.TEXT
    });
  }
});

This behavior is not recommended and should be removed once we're ready for production. A conditioned environment variable might be an appropriate solution.

Note how we use the .collection property to get the actual Mongo.Collection instance. In the Meteor server we want to avoid the usage of observables since it uses fibers. More information about fibers can be fond here.

Preparing Our Client

Now that we have some real data stored in our database, we can replace it with the data fabricated in the ChatsPage. This way the client can stay correlated with the server:

4.4 Use server side data client/imports/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
import { Component, OnInit } from '@angular/core';
import * as Moment from 'moment';
import { Observable } from 'rxjs';
import { Chats, Messages } from '../../../../imports/collections';
import { Chat, MessageType } from '../../../../imports/models';
import template from './chats.html';
 
@Component({
  template
})
export class ChatsPage implements OnInit {
  chats;
 
  constructor() {
  }
 
  ngOnInit() {
    this.chats = Chats
      .find({})
      .mergeMap((chats: Chat[]) =>
        Observable.combineLatest(
          ...chats.map((chat: Chat) =>
            Messages
              .find({chatId: chat._id})
              .startWith(null)
              .map(messages => {
                if (messages) chat.lastMessage = messages[0];
                return chat;
              })
          )
        )
      ).zone();
  }
 
  removeChat(chat: Chat): void {
    this.chats = this.chats.map(chatsArray => {
      const chatIndex = chatsArray.indexOf(chat);
      chatsArray.splice(chatIndex, 1);
 

We will also re-implement the removeChat method using an actual Meteor collection:

4.5 Implement remove chat with the Collection client/imports/pages/chats/chats.ts
33
34
35
36
37
38
  }
 
  removeChat(chat: Chat): void {
    Chats.remove({_id: chat._id}).subscribe(() => {});
  }
}

{{{nav_step prev_ref="https://angular-meteor.com/tutorials/whatsapp2/meteor/rxjs" next_ref="https://angular-meteor.com/tutorials/whatsapp2/meteor/folder-structure"}}}