This step of the tutorial teaches us how to add mobile support for iOS and Android and how to elegantly reuse code using the es2015 modules.
In this tutorial's example we will differentiate the login part of the project: in the browser users will login using email and password and in the mobile app users will login with SMS verification.
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
And now to run in the emulator, run:
$ meteor run ios
# OR
$ meteor run android
You can also run in a real mobile device, for more instructions, read the "Mobile" chapter of the Official Meteor Guide.
Before we can run Meteor and Angular 2 on mobile platform, we need to make sure that our Angular 2 NgModule initialized only when Meteor platform declares that it's ready, to do so, let's wrap the bootstrapModule
with Meteor.startup
:
6
7
8
9
10
11
12
import '../both/methods/parties.methods';
Meteor.startup(() => {
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
});
We're going to keep the view and the component for the web under *.component.web.html
and *.component.web.ts
and doing the same for *.component.mobile.html
and *.component.mobile.ts
.
First thing to do is to rename login.component.html
to login.component.web.html
:
Let's do the same with login.component.ts
file:
with one small change which is a new template
property:
3
4
5
6
7
8
9
import { Router } from '@angular/router';
import { Meteor } from 'meteor/meteor';
import template from './login.component.web.html';
@Component({
selector: 'login',
And let's update the imports in the index file:
1
2
3
4
import {LoginComponent} from "./login.component.web";
import {SignupComponent} from "./signup.component";
import {RecoverComponent} from "./recover.component";
As I mentioned before, we're going to use SMS verification to log in a user on the mobile application.
There is a package for that!
We will use an external package that extends Meteor's Accounts, called accounts-phone that verifies phone number with SMS message, so let's add it:
$ meteor add mys:accounts-phone
Note that in development mode - the SMS will not be sent - and the verification code will be printed to the Meteor log.
We can now move on to create a mobile version Login Component.
A template of a mobile version will be pretty much the same as for browsers:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="md-content" layout="row" layout-align="center start" layout-fill layout-margin>
<div layout="column" flex flex-md="50" flex-lg="50" flex-gt-lg="33" class="md-whiteframe-z2" layout-fill>
<md-toolbar class="md-primary" color="primary">
Sign in
</md-toolbar>
<div layout="column" layout-fill layout-margin layout-padding>
<div layout="row" layout-fill layout-margin>
<p class="md-body-2"> Sign in with SMS</p>
</div>
<div [hidden]="error == ''">
<md-toolbar class="md-warn" layout="row" layout-fill layout-padding layout-margin>
<p class="md-body-1">{{ error }}</p>
</md-toolbar>
</div>
</div>
</div>
</div>
We can use the same directives in the component as in Web version, so let's create a basic component without any functionality:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import template from './login.component.mobile.html';
@Component({
selector: 'login',
template
})
export class MobileLoginComponent implements OnInit {
error: string = '';
constructor(private router: Router, private formBuilder: FormBuilder) {}
ngOnInit() {}
}
SMS verification is a two-step process. First thing to do is to verify a phone number.
Let's create a form for that:
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<p class="md-body-2"> Sign in with SMS</p>
</div>
<form [formGroup]="phoneForm" #f="ngForm" (ngSubmit)="send()"
layout="column" layout-fill layout-padding layout-margin>
<md-input formControlName="phone" type="text" placeholder="Phone"></md-input>
<div layout="row" layout-align="space-between center">
<button md-raised-button class="md-primary" type="submit" aria-label="send">Send SMS</button>
</div>
</form>
<div [hidden]="error == ''">
<md-toolbar class="md-warn" layout="row" layout-fill layout-padding layout-margin>
<p class="md-body-1">{{ error }}</p>
It's a simple form, basically the same as the form with Email and password verification we did in previous chapters.
We can now take care of the logic. Let's create a send
method:
1
2
3
4
5
6
7
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, NgZone} from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { Accounts } from 'meteor/accounts-base';
import template from './login.component.mobile.html';
...some lines skipped...
})
export class MobileLoginComponent implements OnInit {
error: string = '';
phoneForm: FormGroup;
phone: string;
constructor(private router: Router, private zone: NgZone, private formBuilder: FormBuilder) {}
ngOnInit() {
this.phoneForm = this.formBuilder.group({
phone: ['', Validators.required]
});
}
send() {
if (this.phoneForm.valid) {
Accounts.requestPhoneVerification(this.phoneForm.value.phone, (err) => {
this.zone.run(() => {
if (err) {
this.error = err.reason || err;
} else {
this.phone = this.phoneForm.value.phone;
this.error = '';
}
});
});
}
}
}
What we did? Few things:
phoneForm
with one field phone
.send
method that calls Accounts.requestPhoneVerification
to verify phone number and to send SMS with verification code.Great, we're half way there!
Now we need to verify that code. We will keep all the logic under verify
method:
13
14
15
16
17
18
19
20
22
23
24
25
26
27
28
29
30
31
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
error: string = '';
phoneForm: FormGroup;
phone: string;
verifyForm: FormGroup;
isStepTwo: boolean = false;
constructor(private router: Router, private zone: NgZone, private formBuilder: FormBuilder) {}
...some lines skipped...
this.phoneForm = this.formBuilder.group({
phone: ['', Validators.required]
});
this.verifyForm = this.formBuilder.group({
code: ['', Validators.required]
});
}
send() {
...some lines skipped...
} else {
this.phone = this.phoneForm.value.phone;
this.error = '';
this.isStepTwo = true;
}
});
});
}
}
verify() {
if (this.verifyForm.valid) {
Accounts.verifyPhone(this.phone, this.verifyForm.value.code, (err) => {
this.zone.run(() => {
if (err) {
this.error = err.reason || err;
}
else {
this.router.navigate(['/']);
}
});
});
As you can see, we used Accounts.verifyPhone
with proper arguments to call the verification process.
There are two more things that you should notice.
isStepTwo
that holds the status of sign in process. Based on that property we can tell if someone is still in the first phase or he already wants to verify code sent via SMS.PartiesList
if verification succeed.We have all the logic, we still need to create a view for it:
9
10
11
12
13
14
15
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<p class="md-body-2"> Sign in with SMS</p>
</div>
<form [formGroup]="phoneForm" *ngIf="!isStepTwo" #f="ngForm" (ngSubmit)="send()"
layout="column" layout-fill layout-padding layout-margin>
<md-input formControlName="phone" type="text" placeholder="Phone"></md-input>
...some lines skipped...
</div>
</form>
<form *ngIf="isStepTwo" [formGroup]="verifyForm" #f="ngForm" (ngSubmit)="verify()"
layout="column" layout-fill layout-padding layout-margin>
<md-input formControlName="code" type="text" placeholder="Code"></md-input>
<div layout="row" layout-align="space-between center">
<button md-raised-button class="md-primary" type="submit" aria-label="verify">Verify code</button>
</div>
</form>
<div [hidden]="error == ''">
<md-toolbar class="md-warn" layout="row" layout-fill layout-padding layout-margin>
<p class="md-body-1">{{ error }}</p>
And let's add the mobile version of the Component to the index file:
1
2
3
4
5
6
7
8
9
10
11
import {LoginComponent} from "./login.component.web";
import {SignupComponent} from "./signup.component";
import {RecoverComponent} from "./recover.component";
import {MobileLoginComponent} from "./login.component.mobile";
export const AUTH_DECLARATIONS = [
LoginComponent,
SignupComponent,
RecoverComponent,
MobileLoginComponent
];
It seems like both versions are ready.
We can now move on to client/app.routes.ts
.
Just as you can use Meteor.isServer
and Meteor.isClient
to separate your client-side and server-side code, you can use Meteor.isCordova
to separate your Cordova-specific code from the rest of your code.
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { PartiesListComponent } from './parties/parties-list.component';
import { PartyDetailsComponent } from './parties/party-details.component';
import {SignupComponent} from "./auth/signup.component";
import {RecoverComponent} from "./auth/recover.component";
import {MobileLoginComponent} from "./auth/login.component.mobile";
import {LoginComponent} from "./auth/login.component.web";
export const routes: Route[] = [
{ path: '', component: PartiesListComponent },
{ path: 'party/:partyId', component: PartyDetailsComponent, canActivate: ['canActivateForLoggedIn'] },
{ path: 'login', component: Meteor.isCordova ? MobileLoginComponent : LoginComponent },
{ path: 'signup', component: SignupComponent },
{ path: 'recover', component: RecoverComponent }
];
As you can see, we're importing both version of Login Component. But only one is being used, depending on Meteor.isCordova value.
If we would run Socially in a browser LoginComponent
for web platform will be used.
And that's it!
In this tutorial we showed how to make our code behave differently in mobile and web platforms. We did this by creating separate es2015 modules with specific code for mobile and web, and using them based on the platform that runs the application.