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.
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 --example angular2-boilerplate whatsapp
Alternatively, use your web browser to access the link: https://github.com/bsliran/angular2-meteor-base Download the template application, and unzip the files inside. Rename the folder to "whatsapp" and place it into the default directory. Or just use clone the repository.
Now let's see what we've got. Go into the new folder:
$ cd whatsapp
It's a boilerplate that you can use anytime you want to create a project based on angular2-meteor
.
We are going to add our own files for this tutorial. So let's start by deleting most of the contents in these three folders:
- /client (delete)
- /server (delete)
- /both (delete)
Leave only those files:
- /client/index.html
- /client/main.ts
- /client/imports/app/app.component.ts
- /client/imports/app/app.component.html
- /client/imports/app/app.component.scss
- /client/imports/app/app.module.ts
- /server/imports/server-main/main.ts
- /server/main.ts
Let's now update those files:
client/index.html
- stays exactly the same
client/main.ts
- one small change
client/imports/app/app.component.ts
- stays the same
client/imports/app/app.component.html
- remove <demo></demo>
client/imports/app/app.component.scss
- leave it empty
client/imports/app/app.module.ts
- remove Providers and Components
server/main.ts
- without any change
server/imports/server-main/main.ts
- leave only Main class with empty start()
method
1
2
3
<div>
<h1>Hello Angular2-Meteor!</h1>
</div>
1
2
3
4
5
6
7
8
9
10
11
13
14
15
16
17
18
19
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
// Components, Pipes, Directive
declarations: [
AppComponent
],
// Entry Components
entryComponents: [
...some lines skipped...
],
// Providers
providers: [
],
// Modules
imports: [
3
4
5
6
7
8
9
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { Meteor } from "meteor/meteor";
import { AppModule } from './imports/app/app.module';
enableProdMode();
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
"@angular/compiler": "2.1.1",
"@angular/core": "2.1.1",
"@angular/forms": "2.1.1",
"@angular/http": "2.1.1",
"@angular/platform-browser": "2.1.1",
"@angular/platform-browser-dynamic": "2.1.1",
"@angular/platform-server": "2.1.1",
"@angular/router": "3.1.1",
"angular2-meteor": "0.7.1",
"angular2-meteor-polyfills": "0.1.1",
"angular2-meteor-tests-polyfills": "0.0.2",
"meteor-node-stubs": "0.2.4",
"ionic-angular": "^2.0.0-rc.3",
"ionicons": "^3.0.0",
"meteor-rxjs": "^0.4.5",
"reflect-metadata": "0.1.8",
"rxjs": "5.0.0-beta.12",
1
2
3
4
5
export class Main {
start(): void {
}
}
Our project looks clean now. Since we're going to use Ionic, we have to install a proper package:
$ npm install [email protected] --save
It requires few more dependencies:
$ npm install @angular/{http,platform-server} ionicons --save
Great, we have all packages installed, let's move to the more interesting part.
As you probably know, with Angular 2.0 comes NgModule
(see documentation).
Ionic provides their own NgModule, called IonicModule
(see documentation).
Let's use the AppComponent
as the main component of our app (not the root component):
1
2
3
4
5
6
17
18
19
20
21
22
23
24
25
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { IonicApp, IonicModule } from "ionic-angular";
@NgModule({
// Components, Pipes, Directive
...some lines skipped...
],
// Modules
imports: [
IonicModule.forRoot(AppComponent)
],
// Main Component
bootstrap: [ IonicApp ]
})
export class AppModule {}
We removed BrowserModule
since all the declarations and providers are included in IonicModule
.
We also added IonicApp
component which is a root component that lives on top of our AppComponent
.
Now we have to change the root component's selector inside client/index.html
:
2
3
4
5
6
<base href="/">
</head>
<body>
<ion-app>Loading...</ion-app>
</body>
We need to create our own Ionic stylesheet based on the source:
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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
@charset "UTF-8";
@import "{}/node_modules/ionicons/dist/scss/ionicons.scss";
// Shared Variables
// --------------------------------------------------
// To customize the look and feel of this app, you can override
// the Sass variables found in Ionic's source scss files.
// To view all the possible Ionic variables, see:
// http://ionicframework.com/docs/v2/theming/overriding-ionic-variables/
$text-color: #000;
$background-color: #fff;
// 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,
favorite: #69BB7B
);
// 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/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";
// Ionic Variables and Theming. For more info, please see:
// http://ionicframework.com/docs/v2/theming/
@import "{}/node_modules/ionic-angular/themes/ionic.globals.scss";
// 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.scss";
You can just copy paste it.
Ionic looks for fonts in directory we can't access. To fix it, we will use 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
2
3
4
5
6
7
8
9
10
11
{
"map": {
"node_modules/ionic-angular/fonts/roboto-medium.ttf": "fonts/roboto-medium.ttf",
"node_modules/ionic-angular/fonts/roboto-regular.ttf": "fonts/roboto-regular.ttf",
"node_modules/ionic-angular/fonts/roboto-medium.woff": "fonts/roboto-medium.woff",
"node_modules/ionic-angular/fonts/roboto-regular.woff": "fonts/roboto-regular.woff",
"node_modules/ionicons/dist/fonts/ionicons.woff": "fonts/ionicons.woff",
"node_modules/ionicons/dist/fonts/ionicons.woff2": "fonts/ionicons.woff2",
"node_modules/ionicons/dist/fonts/ionicons.ttf": "fonts/ionicons.ttf"
}
}
Now roboto-medium.ttf
is available under http://localhost:3000/fonts/roboto-medium.ttf
.
Yes, with Ionic you're able to use any native functionality you need.
$ npm install ionic-native --save
Now we can use one of those functionalities. Let's work with Status Bar:
1
2
3
4
5
6
8
9
10
11
12
13
14
15
16
17
18
import { Component } from '@angular/core';
import { Platform } from "ionic-angular";
import { StatusBar } from "ionic-native";
import template from './app.component.html';
@Component({
...some lines skipped...
template
})
export class AppComponent {
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.
StatusBar.styleDefault();
});
}
}
Do a quick overview:
platform.ready
returns a Promise
and tell us that the platform is ready and our plugins are availableStatusBar.styleDefault()
makes the app uses the default statusbar (dark text, for light backgrounds).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.
We also need to add few meta tags:
1
2
3
4
5
6
7
8
9
10
<head>
<base href="/">
<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>Loading...</ion-app>
</body>
Now, in order to get smooth mobile experience in Ionic 2, we need to make some modifications to Meteor's default packages.
Meteor comes with a mobile support package called mobile-experience
which is a bundle for three packages: fastclick
, mobile-status-bar
and launch-screen
, and we need to remove fastclick
in order to get better result.
So let's make those changes:
$ meteor remove mobile-experience
$ meteor add mobile-status-bar
$ meteor add launch-screen
You can still use Ionic app in a browser. Just run:
$ meteor
Or with usage of npm script we have predefined in the boilerplate at the very beginning:
$ npm start
Go to http://localhost:3000
to play with the app.
{{{nav_step prev_ref="https://angular-meteor.com/tutorials/whatsapp2-tutorial" next_ref="https://angular-meteor.com/tutorials/whatsapp2/meteor/1.0.0/chats-page"}}}