Fork me on GitHub

WhatsApp Clone with Ionic 2 and Meteor CLI

Socially Merge Version (Last Update: 14.02.2017)

Bootstrapping

If you got directly into here, please read the whole intro section explaining the goals for this tutorial and project.

Both Meteor and Ionic took their platform to the next level in tooling. Both provide CLI interface instead of bringing bunch of dependencies and configure build tools. There are also differences between those tools. in this post we will focus on the Meteor CLI.

If you are interested in the Ionic CLI, the steps needed to use it with Meteor are almost identical to the steps required by the Meteor CLI.

Start by installing Meteor if you haven't already (See reference).

Now let's create our app - write this in the command line:

$ meteor create whatsapp

And let's see what we've got. Go into the new folder:

$ cd whatsapp

A Meteor project will contain the following dirs by default:

  • client - A dir containing all client scripts.
  • server - A dir containing all server scripts.

These scripts should be loaded automatically by their alphabetic order on their belonging platform, e.g. a script defined under the client dir should be loaded by Meteor only on the client. A script defined in neither of these folders should be loaded on both.

Meteor apps use Blaze as its default template engine and Babel as its default script pre-processor. In this tutorial, we're interested in using Angular 2's template engine and Typescript as our script pre-processor; Therefore will remove the blaze-html-templates package:

$ meteor remove blaze-html-templates

And we will replace it with a package called angular2-compilers:

$ meteor add angular2-compilers

The angular2-compilers package not only replace the Blaze template engine, but it also applies Typescript to our Meteor project, as it's the recommended scripting language recommended by the Angular team. In addition, all CSS files will be compiled with a pre-processor called SASS, something which will definitely ease the styling process.

The Typescript compiler operates based on a user defined config, and without it, it's not going to behave expectedly; Therefore, we will define the following Typescript config file:

1.3 Add Typescript config file tsconfig.json
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
{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "baseUrl": ".",
    "declaration": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": [
      "dom",
      "es2015"
    ],
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "target": "es5",
    "skipLibCheck": true,
    "stripInternal": true,
    "noImplicitAny": false,
    "types": [
      "meteor-typings",
      "@types/underscore"
    ]
  },
  "include": [
    "client/**/*.ts",
    "server/**/*.ts",
    "imports/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ],
  "compileOnSave": false,
  "atom": {
    "rewriteTsconfig": false
  }
}

More information regards Typescript configuration file can be found here.

Not all third-party libraries have any support for Typescript whatsoever, something that we should be careful with when building a Typescript app. To allow non-supported third-party libraries, we will add the following declaration file:

1.4 Add declarations file declarations.d.ts
1
declare module '*';

Like most libraries, Angular 2 is relied on peer dependencies, which we will have to make sure exist in our app by installing the following packages:

$ meteor npm install --save @angular/common
$ meteor npm install --save @angular/compiler
$ meteor npm install --save @angular/compiler-cli
$ meteor npm install --save @angular/core
$ meteor npm install --save @angular/forms
$ meteor npm install --save @angular/http
$ meteor npm install --save @angular/platform-browser
$ meteor npm install --save @angular/platform-browser-dynamic
$ meteor npm install --save @angular/platform-server
$ meteor npm install --save meteor-rxjs
$ meteor npm install --save reflect-metadata
$ meteor npm install --save rxjs
$ meteor npm install --save zone.js
$ meteor npm install --save-dev @types/meteor
$ meteor npm install --save-dev @types/meteor-accounts-phone
$ meteor npm install --save-dev @types/underscore
$ meteor npm install --save-dev meteor-typings

Now that we have all the compilers and their dependencies ready, we will have to transform our project files into their supported extension. .js files should be renamed to .ts files, and .css files should be renamed to .scss files.

$ mv server/main.js server/main.ts
$ mv client/main.js client/main.ts
$ mv client/main.css client/main.scss

Last but not least, we will add some basic Cordova plugins which will take care of mobile specific features:

$ meteor add cordova:[email protected]
$ meteor add cordova:[email protected]
$ meteor add cordova:[email protected]
$ meteor add cordova:[email protected]
$ meteor add cordova:[email protected]
$ meteor add cordova:[email protected]

The least above was determined based on Ionic 2's app base.

Everything is set, and we can start using the Angular 2 framework. We will start by setting the basis for our app:

1.8 Prepare application basis client/imports/app/app.component.ts
1
2
3
4
5
6
7
8
import { Component } from [email protected]/core';
import template from "./app.html";
 
@Component({
  selector: 'my-app',
  template
})
export class MyApp {}
1.8 Prepare application basis client/imports/app/app.html
1.8 Prepare application basis client/imports/app/app.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { NgModule, ErrorHandler } from [email protected]/core';
import { MyApp } from './app.component';
 
@NgModule({
  declarations: [
    MyApp
  ],
  entryComponents: [
    MyApp
  ],
  providers: [
    { provide: ErrorHandler }
  ]
})
export class AppModule {}
1.8 Prepare application basis client/imports/app/app.scss
1
2
3
4
5
6
// App Global Sass
// --------------------------------------------------
// Put style rules here that you want to apply globally. These
// styles are for the entire app and not just one component.
// Additionally, this file can be also used as an entry point
// to import other Sass files to be included in the output CSS.
1.8 Prepare application basis client/main.html
3
4
5
6
7
</head>
 
<body>
  <my-app></my-app>
</body>
1.8 Prepare application basis client/main.scss
1
2
// App
@import "imports/app/app";
1.8 Prepare application basis client/main.ts
1
2
3
4
5
6
7
8
9
10
import 'zone.js';
import 'reflect-metadata';
 
import { platformBrowserDynamic } from [email protected]/platform-browser-dynamic';
import { Meteor } from 'meteor/meteor';
import { AppModule } from './imports/app/app.module';
 
Meteor.startup(() => {
  platformBrowserDynamic().bootstrapModule(AppModule);
});
1.8 Prepare application basis declarations.d.ts
1
2
3
4
5
/*
  A wildcard module is declared below to allow third party libraries to be used in an app even if they don't
  provide their own type declarations.
 */
declare module '*';

In the step above, we simply created the entry module and component for our application, so as we go further in this tutorial and add more features, we can simply easily extend our module and app main component.

Ionic 2

Ionic is a free and open source mobile SDK for developing native and progressive web apps with ease. When using IonicCLI, it comes with a boilerplate, just like Meteor does, but since we're not using Meteor CLI and not Ionic's, we will have to set it up manually.

The first thing we're going to do in order to integrate Ionic 2 in our app would be installing its NPM dependencies:

$ meteor npm install --save @ionic/storage
$ meteor npm install --save ionic-angular
$ meteor npm install --save ionic-native
$ meteor npm install --save ionicons

Ionic build system comes with a built-in theming system which helps its users design their app. It's a powerful tool which we wanna take advantage of. In-order to do that, we will define the following SCSS file, and we will import it:

1.10 Add theme variables client/imports/theme/components.scss
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// Components
// --------------------------------------------------
 
@import
"{}/node_modules/ionic-angular/components/action-sheet/action-sheet",
"{}/node_modules/ionic-angular/components/action-sheet/action-sheet.ios",
"{}/node_modules/ionic-angular/components/action-sheet/action-sheet.md",
"{}/node_modules/ionic-angular/components/action-sheet/action-sheet.wp";
 
@import
"{}/node_modules/ionic-angular/components/alert/alert",
"{}/node_modules/ionic-angular/components/alert/alert.ios",
"{}/node_modules/ionic-angular/components/alert/alert.md",
"{}/node_modules/ionic-angular/components/alert/alert.wp";
 
@import
"{}/node_modules/ionic-angular/components/app/app",
"{}/node_modules/ionic-angular/components/app/app.ios",
"{}/node_modules/ionic-angular/components/app/app.md",
"{}/node_modules/ionic-angular/components/app/app.wp";
 
@import
"{}/node_modules/ionic-angular/components/backdrop/backdrop";
 
@import
"{}/node_modules/ionic-angular/components/badge/badge",
"{}/node_modules/ionic-angular/components/badge/badge.ios",
"{}/node_modules/ionic-angular/components/badge/badge.md",
"{}/node_modules/ionic-angular/components/badge/badge.wp";
 
@import
"{}/node_modules/ionic-angular/components/button/button",
"{}/node_modules/ionic-angular/components/button/button-icon",
"{}/node_modules/ionic-angular/components/button/button.ios",
"{}/node_modules/ionic-angular/components/button/button.md",
"{}/node_modules/ionic-angular/components/button/button.wp";
 
@import
"{}/node_modules/ionic-angular/components/card/card",
"{}/node_modules/ionic-angular/components/card/card.ios",
"{}/node_modules/ionic-angular/components/card/card.md",
"{}/node_modules/ionic-angular/components/card/card.wp";
 
@import
"{}/node_modules/ionic-angular/components/checkbox/checkbox.ios",
"{}/node_modules/ionic-angular/components/checkbox/checkbox.md",
"{}/node_modules/ionic-angular/components/checkbox/checkbox.wp";
 
@import
"{}/node_modules/ionic-angular/components/chip/chip",
"{}/node_modules/ionic-angular/components/chip/chip.ios",
"{}/node_modules/ionic-angular/components/chip/chip.md",
"{}/node_modules/ionic-angular/components/chip/chip.wp";
 
@import
"{}/node_modules/ionic-angular/components/content/content",
"{}/node_modules/ionic-angular/components/content/content.ios",
"{}/node_modules/ionic-angular/components/content/content.md",
"{}/node_modules/ionic-angular/components/content/content.wp";
 
@import
"{}/node_modules/ionic-angular/components/datetime/datetime",
"{}/node_modules/ionic-angular/components/datetime/datetime.ios",
"{}/node_modules/ionic-angular/components/datetime/datetime.md",
"{}/node_modules/ionic-angular/components/datetime/datetime.wp";
 
@import
"{}/node_modules/ionic-angular/components/fab/fab",
"{}/node_modules/ionic-angular/components/fab/fab.ios",
"{}/node_modules/ionic-angular/components/fab/fab.md",
"{}/node_modules/ionic-angular/components/fab/fab.wp";
 
@import
"{}/node_modules/ionic-angular/components/grid/grid";
 
@import
"{}/node_modules/ionic-angular/components/icon/icon",
"{}/node_modules/ionic-angular/components/icon/icon.ios",
"{}/node_modules/ionic-angular/components/icon/icon.md",
"{}/node_modules/ionic-angular/components/icon/icon.wp";
 
@import
"{}/node_modules/ionic-angular/components/img/img";
 
@import
"{}/node_modules/ionic-angular/components/infinite-scroll/infinite-scroll";
 
@import
"{}/node_modules/ionic-angular/components/input/input",
"{}/node_modules/ionic-angular/components/input/input.ios",
"{}/node_modules/ionic-angular/components/input/input.md",
"{}/node_modules/ionic-angular/components/input/input.wp";
 
@import
"{}/node_modules/ionic-angular/components/item/item",
"{}/node_modules/ionic-angular/components/item/item-media",
"{}/node_modules/ionic-angular/components/item/item-reorder",
"{}/node_modules/ionic-angular/components/item/item-sliding",
"{}/node_modules/ionic-angular/components/item/item.ios",
"{}/node_modules/ionic-angular/components/item/item.md",
"{}/node_modules/ionic-angular/components/item/item.wp";
 
@import
"{}/node_modules/ionic-angular/components/label/label",
"{}/node_modules/ionic-angular/components/label/label.ios",
"{}/node_modules/ionic-angular/components/label/label.md",
"{}/node_modules/ionic-angular/components/label/label.wp";
 
@import
"{}/node_modules/ionic-angular/components/list/list",
"{}/node_modules/ionic-angular/components/list/list.ios",
"{}/node_modules/ionic-angular/components/list/list.md",
"{}/node_modules/ionic-angular/components/list/list.wp";
 
@import
"{}/node_modules/ionic-angular/components/loading/loading",
"{}/node_modules/ionic-angular/components/loading/loading.ios",
"{}/node_modules/ionic-angular/components/loading/loading.md",
"{}/node_modules/ionic-angular/components/loading/loading.wp";
 
@import
"{}/node_modules/ionic-angular/components/menu/menu",
"{}/node_modules/ionic-angular/components/menu/menu.ios",
"{}/node_modules/ionic-angular/components/menu/menu.md",
"{}/node_modules/ionic-angular/components/menu/menu.wp";
 
@import
"{}/node_modules/ionic-angular/components/modal/modal",
"{}/node_modules/ionic-angular/components/modal/modal.ios",
"{}/node_modules/ionic-angular/components/modal/modal.md",
"{}/node_modules/ionic-angular/components/modal/modal.wp";
 
@import
"{}/node_modules/ionic-angular/components/note/note.ios",
"{}/node_modules/ionic-angular/components/note/note.md",
"{}/node_modules/ionic-angular/components/note/note.wp";
 
@import
"{}/node_modules/ionic-angular/components/picker/picker",
"{}/node_modules/ionic-angular/components/picker/picker.ios",
"{}/node_modules/ionic-angular/components/picker/picker.md",
"{}/node_modules/ionic-angular/components/picker/picker.wp";
 
@import
"{}/node_modules/ionic-angular/components/popover/popover",
"{}/node_modules/ionic-angular/components/popover/popover.ios",
"{}/node_modules/ionic-angular/components/popover/popover.md",
"{}/node_modules/ionic-angular/components/popover/popover.wp";
 
@import
"{}/node_modules/ionic-angular/components/radio/radio.ios",
"{}/node_modules/ionic-angular/components/radio/radio.md",
"{}/node_modules/ionic-angular/components/radio/radio.wp";
 
@import
"{}/node_modules/ionic-angular/components/range/range",
"{}/node_modules/ionic-angular/components/range/range.ios",
"{}/node_modules/ionic-angular/components/range/range.md",
"{}/node_modules/ionic-angular/components/range/range.wp";
 
@import
"{}/node_modules/ionic-angular/components/refresher/refresher";
 
@import
"{}/node_modules/ionic-angular/components/scroll/scroll";
 
@import
"{}/node_modules/ionic-angular/components/searchbar/searchbar",
"{}/node_modules/ionic-angular/components/searchbar/searchbar.ios",
"{}/node_modules/ionic-angular/components/searchbar/searchbar.md",
"{}/node_modules/ionic-angular/components/searchbar/searchbar.wp";
 
@import
"{}/node_modules/ionic-angular/components/segment/segment",
"{}/node_modules/ionic-angular/components/segment/segment.ios",
"{}/node_modules/ionic-angular/components/segment/segment.md",
"{}/node_modules/ionic-angular/components/segment/segment.wp";
 
@import
"{}/node_modules/ionic-angular/components/select/select",
"{}/node_modules/ionic-angular/components/select/select.ios",
"{}/node_modules/ionic-angular/components/select/select.md",
"{}/node_modules/ionic-angular/components/select/select.wp";
 
@import
"{}/node_modules/ionic-angular/components/show-hide-when/show-hide-when";
 
@import
"{}/node_modules/ionic-angular/components/slides/slides";
 
@import
"{}/node_modules/ionic-angular/components/spinner/spinner",
"{}/node_modules/ionic-angular/components/spinner/spinner.ios",
"{}/node_modules/ionic-angular/components/spinner/spinner.md",
"{}/node_modules/ionic-angular/components/spinner/spinner.wp";
 
@import
"{}/node_modules/ionic-angular/components/tabs/tabs",
"{}/node_modules/ionic-angular/components/tabs/tabs.ios",
"{}/node_modules/ionic-angular/components/tabs/tabs.md",
"{}/node_modules/ionic-angular/components/tabs/tabs.wp";
 
@import
"{}/node_modules/ionic-angular/components/toast/toast",
"{}/node_modules/ionic-angular/components/toast/toast.ios",
"{}/node_modules/ionic-angular/components/toast/toast.md",
"{}/node_modules/ionic-angular/components/toast/toast.wp";
 
@import
"{}/node_modules/ionic-angular/components/toggle/toggle.ios",
"{}/node_modules/ionic-angular/components/toggle/toggle.md",
"{}/node_modules/ionic-angular/components/toggle/toggle.wp";
 
@import
"{}/node_modules/ionic-angular/components/toolbar/toolbar",
"{}/node_modules/ionic-angular/components/toolbar/toolbar-button",
"{}/node_modules/ionic-angular/components/toolbar/toolbar.ios",
"{}/node_modules/ionic-angular/components/toolbar/toolbar.md",
"{}/node_modules/ionic-angular/components/toolbar/toolbar.wp";
 
@import
"{}/node_modules/ionic-angular/components/typography/typography",
"{}/node_modules/ionic-angular/components/typography/typography.ios",
"{}/node_modules/ionic-angular/components/typography/typography.md",
"{}/node_modules/ionic-angular/components/typography/typography.wp";
 
@import
"{}/node_modules/ionic-angular/components/virtual-scroll/virtual-scroll";
 
 
// Platforms
// --------------------------------------------------
@import
"{}/node_modules/ionic-angular/platform/cordova",
"{}/node_modules/ionic-angular/platform/cordova.ios",
"{}/node_modules/ionic-angular/platform/cordova.md",
"{}/node_modules/ionic-angular/platform/cordova.wp";
 
 
// Fonts
// --------------------------------------------------
@import
"ionicons",
"{}/node_modules/ionic-angular/fonts/noto-sans",
"{}/node_modules/ionic-angular/fonts/roboto";
1.10 Add theme variables client/imports/theme/ionicons.scss
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
// Ionicons Icon Font CSS
// --------------------------
// Ionicons CSS for Ionic's <ion-icon> element
// ionicons-icons.scss has the icons and their unicode characters
 
$ionicons-font-path: $font-path !default;
 
@import "{}/node_modules/ionicons/dist/scss/ionicons-icons";
@import "{}/node_modules/ionicons/dist/scss/ionicons-variables";
 
 
@font-face {
  font-family: "Ionicons";
  src: url("#{$ionicons-font-path}/ionicons.woff2?v=#{$ionicons-version}") format("woff2"),
    url("#{$ionicons-font-path}/ionicons.woff?v=#{$ionicons-version}") format("woff"),
    url("#{$ionicons-font-path}/ionicons.ttf?v=#{$ionicons-version}") format("truetype");
  font-weight: normal;
  font-style: normal;
}
 
ion-icon {
  display: inline-block;
 
  font-family: "Ionicons";
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  font-style: normal;
  font-variant: normal;
  font-weight: normal;
  line-height: 1;
  text-rendering: auto;
  text-transform: none;
  speak: none;
}
1.10 Add theme variables client/imports/theme/variables.scss
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
// Named Color Variables
// --------------------------------------------------
// Named colors makes it easy to reuse colors on various components.
// It's highly recommended to change the default colors
// to match your app's branding. Ionic uses a Sass map of
// colors so you can add, rename and remove colors as needed.
// The "primary" color is the only required color in the map.
 
$colors: (
  primary:    #387ef5,
  secondary:  #32db64,
  danger:     #f53d3d,
  light:      #f4f4f4,
  dark:       #222
);
 
// Components
// --------------------------------------------------
 
@import "components";
 
 
// App Theme
// --------------------------------------------------
// Ionic apps can have different themes applied, which can
// then be future customized. This import comes last
// so that the above variables are used and Ionic's
// default are overridden.
 
@import "{}/node_modules/ionic-angular/themes/ionic.theme.default";
1.10 Add theme variables client/main.scss
1
2
3
4
5
// Theme
@import "imports/theme/variables";
 
// App
@import "imports/app/app";

The variables.scss file is hooked to different Ionic packages located in the node_modules dir. It means that the logic already existed, we only bound all the necessary modules together in order to emulate Ionic's theming system.

Ionic looks for fonts in directory we can't access. To fix it, we will use the mys:font package to teach Meteor how to put them there.

$ meteor add mys:fonts

That plugin needs to know which font we want to use and where it should be available.

Configuration is pretty easy, you will catch it by just looking on an example:

1.12 Add fonts mapping file fonts.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
  "extensions": ["eot", "ttf", "woff", "woff2"],
  "map": {
    "node_modules/ionic-angular/fonts/ionicons.eot": "fonts/ionicons.eot",
    "node_modules/ionic-angular/fonts/ionicons.ttf": "fonts/ionicons.ttf",
    "node_modules/ionic-angular/fonts/ionicons.woff": "fonts/ionicons.woff",
    "node_modules/ionic-angular/fonts/ionicons.woff2": "fonts/ionicons.woff2",
    "node_modules/ionic-angular/fonts/noto-sans-bold.ttf": "fonts/noto-sans-bold.ttf",
    "node_modules/ionic-angular/fonts/noto-sans-regular.ttf": "fonts/noto-sans-regular.ttf",
    "node_modules/ionic-angular/fonts/noto-sans.scss": "fonts/noto-sans.scss",
    "node_modules/ionic-angular/fonts/roboto-bold.ttf": "fonts/roboto-bold.ttf",
    "node_modules/ionic-angular/fonts/roboto-bold.woff": "fonts/roboto-bold.woff",
    "node_modules/ionic-angular/fonts/roboto-light.ttf": "fonts/roboto-light.ttf",
    "node_modules/ionic-angular/fonts/roboto-light.woff": "fonts/roboto-light.woff",
    "node_modules/ionic-angular/fonts/roboto-medium.ttf": "fonts/roboto-medium.ttf",
    "node_modules/ionic-angular/fonts/roboto-medium.woff": "fonts/roboto-medium.woff",
    "node_modules/ionic-angular/fonts/roboto-regular.ttf": "fonts/roboto-regular.ttf",
    "node_modules/ionic-angular/fonts/roboto-regular.woff": "fonts/roboto-regular.woff"
  }
}

Ionic is set. We will have to make few adjustments in our app in order to use Ionic, mostly importing its modules and using its components:

1.13 Add Ionic to application basis client/imports/app/app.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Component } from [email protected]/core';
import { Platform } from 'ionic-angular';
import { StatusBar, Splashscreen } from 'ionic-native';
import template from "./app.html";
 
@Component({
  template
})
export class MyApp {
  constructor(platform: Platform) {
    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      if (platform.is('cordova')) {
        StatusBar.styleDefault();
        Splashscreen.hide();
      }
    });
  }
}
1.13 Add Ionic to application basis client/imports/app/app.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { NgModule, ErrorHandler } from [email protected]/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
 
@NgModule({
  declarations: [
    MyApp
  ],
  imports: [
    IonicModule.forRoot(MyApp),
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp
  ],
  providers: [
    { provide: ErrorHandler, useClass: IonicErrorHandler }
  ]
})
export class AppModule {}
1.13 Add Ionic to application basis client/main.html
1
2
3
4
5
6
7
8
9
10
11
<head>
  <title>Ionic2-MeteorCLI-WhatsApp</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <meta name="format-detection" content="telephone=no">
  <meta name="msapplication-tap-highlight" content="no">
  <meta name="theme-color" content="#4e8ef7">
</head>
 
<body>
  <ion-app></ion-app>
</body>

Running on Mobile

To add mobile support, select the platform(s) you want and run the following command:

$ meteor add-platform ios
# OR / AND
$ meteor add-platform android

To run an app in the emulator use:

$ meteor run ios
# OR
$ meteor run android

To learn more about Mobile in Meteor read the "Mobile" chapter of the Meteor Guide.

Meteor projects come with a package called mobile-experience by default, which is a bundle of fastclick, mobile-status-bar and launch-screen. The fastclick package might cause some conflicts between Meteor and Ionic's functionality, something which will probably cause some unexpected behavior. To fix it, we're going to remove mobile-experience and install the rest of its packages explicitly:

$ meteor remove mobile-experience
$ meteor add mobile-status-bar
$ meteor add launch-screen

Web

Ionic apps are still usable in the browser. You can run them using the command:

$ meteor start
# OR
$ npm start

The app should be running on port 3000, and can be changed by specifying a --port option.

{{{nav_step prev_ref="https://angular-meteor.com/tutorials/whatsapp2-tutorial" next_ref="https://angular-meteor.com/tutorials/whatsapp2/meteor/chats-page"}}}