In this chapter we will add angular2-material to our project, and update some style and layout in the project.
Angular2-material documentation of each component can be found here.
First, let's remove our previous framework (Bootstrap) by running:
$ meteor npm uninstall --save bootstrap
And let's remove the import from the main.sass
file:
1
2
3
@import "./imports/app/colors.scss";
html, body {
Now we need to add angular2-material to our project - so let's do that.
Run the following command in your Terminal:
$ meteor npm install @angular/[email protected]
Now let's load the module into our NgModule
:
10
11
12
13
14
15
16
22
23
24
25
26
27
28
29
import { routes, ROUTES_PROVIDERS } from './app.routes';
import { PARTIES_DECLARATIONS } from './parties';
import { SHARED_DECLARATIONS } from './shared';
import { MaterialModule } from "@angular/material";
@NgModule({
imports: [
...some lines skipped...
Ng2PaginationModule,
AgmCoreModule.forRoot({
apiKey: 'AIzaSyAWoBdZHCNh5R-hB5S5ZZ2oeoYyfdDgniA'
}),
MaterialModule.forRoot()
],
declarations: [
AppComponent,
Like we did in the previous chapter - let's take care of the navigation bar first.
We will use directives and components from Angular2-Material - such as md-toolbar
.
Let's use it in the app component's template:
1
2
3
4
5
6
<md-toolbar color="primary">
<a routerLink="/" class="toolbar-title">Socially2</a>
<span class="fill-remaining-space"></span>
<login-buttons></login-buttons>
</md-toolbar>
<router-outlet></router-outlet>
And let's create a stylesheet file for the AppComponent
:
1
2
3
4
5
6
7
8
.toolbar-title {
text-decoration: none;
color: white;
}
md-toolbar {
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.26);
}
And import it into our Component:
1
2
3
4
5
6
7
8
9
10
11
import { Component } from '@angular/core';
import template from './app.component.html';
import style from './app.component.scss';
@Component({
selector: 'app',
template,
styles: [ style ]
})
export class AppComponent {}
And let's add .fill-remaining-space
CSS class we used, and let's create a Angular 2 Material theme with the colors we like (full documentation about themes is here)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@import '../node_modules/@angular/material/core/theming/all-theme';
@include md-core();
$app-primary: md-palette($md-light-blue, 500, 100, 700);
$app-accent: md-palette($md-pink, A200, A100, A400);
$app-warn: md-palette($md-red);
$app-theme: md-light-theme($app-primary, $app-accent, $app-warn);
@include angular-material-theme($app-theme);
.fill-remaining-space {
flex: 1 1 auto;
}
body {
background-color: #f8f8f8;
font-family: 'Muli', sans-serif;
padding: 0;
margin: 0;
}
.sebm-google-map-container {
width: 450px;
height: 450px;
}
Let's replace the label
and the input
with simply the md-input
and md-checkbox
and make the button
look material:
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
<div class="form-container">
<div class="container-background">
<div class="form-content">
<div class="form-center">
<h1>Your party is missing?</h1>
<h2>Add it now! ></h2>
</div>
<div class="form-center">
<form *ngIf="user" [formGroup]="addForm" (ngSubmit)="addParty()">
<div style="display: table-row">
<div class="form-inputs">
<md-input dividerColor="accent" formControlName="name" placeholder="Party name"></md-input>
<br/>
<md-input dividerColor="accent" formControlName="description" placeholder="Description"></md-input>
<br/>
<md-input dividerColor="accent" formControlName="location" placeholder="Location name"></md-input>
<br/>
<md-checkbox formControlName="public">Public party?</md-checkbox>
<br/><br/>
<button color="accent" md-raised-button type="submit">Add my party!</button>
</div>
<div class="form-extras">
<sebm-google-map class="new-party-map"
[latitude]="newPartyPosition.lat"
[longitude]="newPartyPosition.lng"
[zoom]="8"
(mapClick)="mapClicked($event)">
<sebm-google-map-marker
[latitude]="newPartyPosition.lat"
[longitude]="newPartyPosition.lng">
</sebm-google-map-marker>
</sebm-google-map>
</div>
</div>
</form>
<div *ngIf="!user">
Please login in order to create new parties!
</div>
</div>
</div>
</div>
</div>
We use the mdInput
component which is a wrapper for regular HTML input with style and cool layout.
Now let's add CSS styles:
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
.form-container {
position: relative;
display: inline-block;
overflow-y: auto;
overflow-x: hidden;
flex-grow: 1;
z-index: 1;
width: 100%;
color: white;
.container-background {
background: linear-gradient(rgb(0,121,107),rgb(0,150,136));
color: #fff;
.form-content {
background: #0277bd;
width: 100%;
padding: 0 !important;
align-items: center;
display: flex;
flex-flow: row wrap;
margin: 0 auto;
form {
width: 100%;
display: table;
}
.form-inputs {
display: table-cell;
width: 60%;
vertical-align: top;
text-align: center;
margin-top: 20px;
}
.form-extras {
display: table-cell;
width: 40%;
vertical-align: top;
.new-party-map {
width: 100% !important;
height: 300px !important;
}
}
.form-center {
width: 50%;
text-align: center;
}
}
}
}
Now we need to make some changes in our Component's code - we will inject the user (using InjectUser
), import the new stylesheet and add the ability to set the new party location using a Google map Component:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
28
29
30
31
32
33
34
35
36
37
43
44
45
46
47
48
49
50
51
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Parties } from '../../../../both/collections/parties.collection';
import { InjectUser } from "angular2-meteor-accounts-ui";
import template from './parties-form.component.html';
import style from './parties-form.component.scss';
@Component({
selector: 'parties-form',
template,
styles: [ style ]
})
@InjectUser("user")
export class PartiesFormComponent implements OnInit {
addForm: FormGroup;
newPartyPosition: {lat:number, lng: number} = {lat: 37.4292, lng: -122.1381};
constructor(
private formBuilder: FormBuilder
...some lines skipped...
});
}
mapClicked($event) {
this.newPartyPosition = $event.coords;
}
addParty(): void {
if (!Meteor.userId()) {
alert('Please log in to add a party');
...some lines skipped...
name: this.addForm.value.name,
description: this.addForm.value.description,
location: {
name: this.addForm.value.location,
lat: this.newPartyPosition.lat,
lng: this.newPartyPosition.lng
},
public: this.addForm.value.public,
owner: Meteor.userId()
PartiesForm component is done, so we can move one level higher in the component's tree. Time for the list of parties:
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
<parties-form></parties-form>
<div class="parties-list-container">
<div class="parties-list">
<md-card class="filter-card">
<h3>Filter Parties</h3>
<form>
By Location: <md-input dividerColor="primary" type="text" #searchtext placeholder="Enter Location"></md-input>
<button md-button (click)="search(searchtext.value)">Find</button>
<br />
Sort by name:
<select class="form-control" #sort (change)="changeSortOrder(sort.value)">
<option value="1" selected>Ascending</option>
<option value="-1">Descending</option>
</select>
</form>
</md-card>
<pagination-controls class="pagination" (pageChange)="onPageChanged($event)"></pagination-controls>
<md-card *ngFor="let party of parties | async" class="party-card">
<h2 class="party-name">
<a [routerLink]="['/party', party._id]">{{party.name}}</a>
</h2>
@ {{party.location.name}}
<br />
{{party.description}}
<button class="remove-party" md-icon-button *ngIf="isOwner(party)" (click)="removeParty(party)">
<md-icon class="md-24">X</md-icon>
</button>
<div class="rsvp-container">
<div class="rsvps-sum">
<div class="rsvps-amount">{{party | rsvp:'yes'}}</div>
<div class="rsvps-title">Yes</div>
</div>
<div class="rsvps-sum">
<div class="rsvps-amount">{{party | rsvp:'maybe'}}</div>
<div class="rsvps-title">Maybe</div>
</div>
<div class="rsvps-sum">
<div class="rsvps-amount">{{party | rsvp:'no'}}</div>
<div class="rsvps-title">No</div>
</div>
</div>
</md-card>
<pagination-controls class="pagination" (pageChange)="onPageChanged($event)"></pagination-controls>
</div>
<div class="parties-map">
<sebm-google-map
[latitude]="0"
[longitude]="0"
[zoom]="1"
class="google-map">
<div *ngFor="let party of parties | async">
<sebm-google-map-marker
*ngIf="party.location.lat"
[latitude]="party.location.lat"
[longitude]="party.location.lng">
</sebm-google-map-marker>
</div>
</sebm-google-map>
</div>
</div>
To make it all look so much better, let's add couple of rules to css:
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
.parties-list-container {
align-items: center;
display: flex;
flex-flow: row wrap;
margin: 0 auto;
width: 100%;
display: table;
.parties-list {
display: table-cell;
width: 50%;
vertical-align: top;
.pagination {
display: inline;
text-align: center;
}
.filter-card {
margin: 20px;
}
.party-card {
margin: 20px;
position: relative;
.party-name > a {
color: black;
text-decoration: none;
}
.remove-party {
position: absolute;
top: 10px;
right: 10px;
}
.rsvp-container {
position: absolute;
bottom: 10px;
right: 10px;
.rsvps-sum {
display: inline-block;
width: 50px;
text-align: center;
.rsvps-amount {
font-size: 24px;
}
.rsvps-title {
font-size: 13px;
color: #aaa;
}
}
}
}
}
.parties-map {
display: table-cell;
width: 50%;
vertical-align: top;
.google-map {
width: 100%;
min-height: 600px;
}
}
}
We also need to update the PartyDetails component:
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
<div class="party-details-container" *ngIf="party">
<div class="row">
<div class="party-details">
<md-card>
<h2>{{ party.name }}</h2>
<form layout="column" (submit)="saveParty()">
<label>Party Name: </label>
<md-input [disabled]="!isOwner" [(ngModel)]="party.name" name="name"></md-input>
<br />
<label>Party Description: </label>
<md-input [disabled]="!isOwner" [(ngModel)]="party.description" name="description"></md-input>
<br />
<label>Location name: </label>
<md-input [disabled]="!isOwner" [(ngModel)]="party.location.name" name="location"></md-input>
<br />
<md-checkbox [disabled]="!isOwner" [(checked)]="party.public" name="public" aria-label="Public">
Public party?
</md-checkbox>
<div layout="row" layout-align="left">
<button [disabled]="!isOwner" type="submit" md-raised-button color="accent">Save</button>
<a [routerLink]="['/']" md-raised-button class="md-raised">Back</a>
</div>
</form>
</md-card>
</div>
<div class="party-invites">
<md-card>
<h2>Invitations</h2>
<span [hidden]="!party.public">Public party, no need for invitations!</span>
<md-list>
<md-list-item *ngFor="let user of users | async">
<div>{{ user | displayName }}</div>
<button (click)="invite(user)" md-raised-button>Invite</button>
</md-list-item>
</md-list>
</md-card>
</div>
<div class="party-map">
<md-card>
<h2>Party location</h2>
<sebm-google-map
[latitude]="lat || centerLat"
[longitude]="lng || centerLng"
[zoom]="8"
(mapClick)="mapClicked($event)">
<sebm-google-map-marker
*ngIf="lat && lng"
[latitude]="lat"
[longitude]="lng">
</sebm-google-map-marker>
</sebm-google-map>
</md-card>
</div>
</div>
</div>
And let's update the styles:
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
.party-details-container {
display: table;
.row {
display: table-row;
width: 100%;
.party-details, .party-invites, .party-map {
display: table-cell;
width: 33.3%;
vertical-align: top;
md-card {
margin: 20px;
}
}
.party-map {
sebm-google-map {
height: 300px;
width: 100%;
}
}
}
}
In this point, you can also remove the
colors.scss
- it's no longer in use!
Our next step will replace the login-buttons
which is a simple and non-styled login/signup component - we will add our custom authentication component with custom style.
First, let's remove the login-buttons from the navigation bar, and replace it with custom buttons for Login / Signup / Logout.
We will also add routerLink
to each button, and add logic to hide/show buttons according to the user's login state:
1
2
3
4
5
6
7
8
9
10
11
12
13
<md-toolbar color="primary">
<a routerLink="/" class="toolbar-title">Socially2</a>
<span class="fill-remaining-space"></span>
<div [hidden]="user">
<a md-button [routerLink]="['/login']" >Login</a>
<a md-button [routerLink]="['/signup']">Sign up</a>
</div>
<div [hidden]="!user">
<span>{{ user | displayName }}</span>
<button md-button (click)="logout()">Logout</button>
</div>
</md-toolbar>
<router-outlet></router-outlet>
Let's use InjectUser
decorator, just like we did in one of the previous chapters.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import template from './app.component.html';
import style from './app.component.scss';
import {InjectUser} from "angular2-meteor-accounts-ui";
@Component({
selector: 'app',
template,
styles: [ style ]
})
@InjectUser('user')
export class AppComponent {
constructor() {
}
logout() {
Meteor.logout();
}
}
As you can see, we used DisplayNamePipe
in the view so we have to import it.
We also implemented logout()
method with Meteor.logout()
. It is, like you probably guessed, to log out the current user.
Now we can move on to create three new components.
First component, is to log in user to the app.
We will need a form and the login method, so let's implement them:
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
import {Component, OnInit, NgZone} from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { Meteor } from 'meteor/meteor';
import template from './login.component.html';
@Component({
selector: 'login',
template
})
export class LoginComponent implements OnInit {
loginForm: FormGroup;
error: string;
constructor(private router: Router, private zone: NgZone, private formBuilder: FormBuilder) {}
ngOnInit() {
this.loginForm = this.formBuilder.group({
email: ['', Validators.required],
password: ['', Validators.required]
});
this.error = '';
}
login() {
if (this.loginForm.valid) {
Meteor.loginWithPassword(this.loginForm.value.email, this.loginForm.value.password, (err) => {
this.zone.run(() => {
if (err) {
this.error = err;
} else {
this.router.navigate(['/']);
}
});
});
}
}
}
Notice that we used
NgZone
in our constructor in order to get it from the Dependency Injection, and we used it before we update the result of the login action - we need to do this because the Meteor world does not update Angular's world, and we need to tell Angular when to update the view since the async result of the login action comes from Meteor's context.
You previously created a form by yourself so there's no need to explain the whole process once again.
About the login method.
Meteor's accounts system has a method called loginWithPassword
, you can read more about it here.
We need to provide two values, a email and a password. We could get them from the form.
In the callback of Meteor.loginWithPassword's method, we have the redirection to the homepage on success and we're saving the error message if login process failed.
Let's add the 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
<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-margin layout-padding>
<div layout="row" layout-margin>
<p class="md-body-2"> Sign in with your email</p>
</div>
<form [formGroup]="loginForm" #f="ngForm" (ngSubmit)="login()"
layout="column" layout-fill layout-padding layout-margin>
<md-input formControlName="email" type="email" placeholder="Email"></md-input>
<md-input formControlName="password" type="password" placeholder="Password"></md-input>
<div layout="row" layout-align="space-between center">
<a md-button [routerLink]="['/recover']">Forgot password?</a>
<button md-raised-button class="md-primary" type="submit" aria-label="login">Sign In</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>
</md-toolbar>
</div>
<md-divider></md-divider>
<div layout="row" layout-align="center">
<a md-button [routerLink]="['/signup']">Need an account?</a>
</div>
</div>
</div>
</div>
We also need to define the /login
route:
3
4
5
6
7
8
9
10
11
12
13
14
import { PartiesListComponent } from './parties/parties-list.component';
import { PartyDetailsComponent } from './parties/party-details.component';
import {LoginComponent} from "./auth/login.component";
export const routes: Route[] = [
{ path: '', component: PartiesListComponent },
{ path: 'party/:partyId', component: PartyDetailsComponent, canActivate: ['canActivateForLoggedIn'] },
{ path: 'login', component: LoginComponent }
];
export const ROUTES_PROVIDERS = [{
And now let's create an index file for the auth files:
1
2
3
4
5
import {LoginComponent} from "./login.component";
export const AUTH_DECLARATIONS = [
LoginComponent
];
And import the exposed Array into the NgModule
:
11
12
13
14
15
16
17
29
30
31
32
33
34
35
36
import { PARTIES_DECLARATIONS } from './parties';
import { SHARED_DECLARATIONS } from './shared';
import { MaterialModule } from "@angular/material";
import { AUTH_DECLARATIONS } from "./auth/index";
@NgModule({
imports: [
...some lines skipped...
declarations: [
AppComponent,
...PARTIES_DECLARATIONS,
...SHARED_DECLARATIONS,
...AUTH_DECLARATIONS
],
providers: [
...ROUTES_PROVIDERS
The Signup component looks pretty much the same as the Login component. We just use different method, Accounts.createUser()
. Here's the link to the documentation.
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
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 './signup.component.html';
@Component({
selector: 'signup',
template
})
export class SignupComponent implements OnInit {
signupForm: FormGroup;
error: string;
constructor(private router: Router, private zone: NgZone, private formBuilder: FormBuilder) {}
ngOnInit() {
this.signupForm = this.formBuilder.group({
email: ['', Validators.required],
password: ['', Validators.required]
});
this.error = '';
}
signup() {
if (this.signupForm.valid) {
Accounts.createUser({
email: this.signupForm.value.email,
password: this.signupForm.value.password
}, (err) => {
if (err) {
this.zone.run(() => {
this.error = err;
});
} else {
this.router.navigate(['/']);
}
});
}
}
}
And the 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
<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 up
</md-toolbar>
<div layout="column" layout-fill layout-margin layout-padding>
<form [formGroup]="signupForm" #f="ngForm" (ngSubmit)="signup()"
layout="column" layout-fill layout-padding layout-margin>
<md-input formControlName="email" type="email" placeholder="Email"></md-input>
<md-input formControlName="password" type="password" placeholder="Password"></md-input>
<div layout="row" layout-align="space-between center">
<button md-raised-button class="md-primary" type="submit" aria-label="login">Sign Up</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>
</md-toolbar>
</div>
<md-divider></md-divider>
<div layout="row" layout-align="center">
<a md-button [routerLink]="['/login']">Already a user?</a>
</div>
</div>
</div>
</div>
And add it to the index file:
1
2
3
4
5
6
7
import {LoginComponent} from "./login.component";
import {SignupComponent} from "./signup.component";
export const AUTH_DECLARATIONS = [
LoginComponent,
SignupComponent
];
And the /signup
route:
4
5
6
7
8
9
10
11
12
13
14
15
16
import { PartiesListComponent } from './parties/parties-list.component';
import { PartyDetailsComponent } from './parties/party-details.component';
import {LoginComponent} from "./auth/login.component";
import {SignupComponent} from "./auth/signup.component";
export const routes: Route[] = [
{ path: '', component: PartiesListComponent },
{ path: 'party/:partyId', component: PartyDetailsComponent, canActivate: ['canActivateForLoggedIn'] },
{ path: 'login', component: LoginComponent },
{ path: 'signup', component: SignupComponent }
];
export const ROUTES_PROVIDERS = [{
This component is helfup when a user forgets his password. We'll use Accounts.forgotPassword
method:
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
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 './recover.component.html';
@Component({
selector: 'recover',
template
})
export class RecoverComponent implements OnInit {
recoverForm: FormGroup;
error: string;
constructor(private router: Router, private zone: NgZone, private formBuilder: FormBuilder) {}
ngOnInit() {
this.recoverForm = this.formBuilder.group({
email: ['', Validators.required]
});
this.error = '';
}
recover() {
if (this.recoverForm.valid) {
Accounts.forgotPassword({
email: this.recoverForm.value.email
}, (err) => {
if (err) {
this.zone.run(() => {
this.error = err;
});
} else {
this.router.navigate(['/']);
}
});
}
}
}
Create the 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
<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">
Recover Your Password
</md-toolbar>
<div layout="column" layout-fill layout-margin layout-padding>
<form [formGroup]="recoverForm" #f="ngForm" (ngSubmit)="recover()"
layout="column" layout-fill layout-padding layout-margin>
<md-input formControlName="email" type="email" placeholder="Email"></md-input>
<div layout="row" layout-align="space-between center">
<button md-raised-button class="md-primary" type="submit" aria-label="Recover">Recover</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>
</md-toolbar>
</div>
<md-divider></md-divider>
<div layout="row" layout-align="center">
<a md-button [routerLink]="['/login']">Remember your password?</a>
</div>
</div>
</div>
</div>
And add it to the index file:
1
2
3
4
5
6
7
8
9
import {LoginComponent} from "./login.component";
import {SignupComponent} from "./signup.component";
import {RecoverComponent} from "./recover.component";
export const AUTH_DECLARATIONS = [
LoginComponent,
SignupComponent,
RecoverComponent
];
And add the /reset
route:
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { PartyDetailsComponent } from './parties/party-details.component';
import {LoginComponent} from "./auth/login.component";
import {SignupComponent} from "./auth/signup.component";
import {RecoverComponent} from "./auth/recover.component";
export const routes: Route[] = [
{ path: '', component: PartiesListComponent },
{ path: 'party/:partyId', component: PartyDetailsComponent, canActivate: ['canActivateForLoggedIn'] },
{ path: 'login', component: LoginComponent },
{ path: 'signup', component: SignupComponent },
{ path: 'recover', component: RecoverComponent }
];
export const ROUTES_PROVIDERS = [{
That's it! we just implemented our own authentication components using Meteor's Accounts API and Angular2-Material!
Note that the recovery email won't be sent to the actual email address, since you need to configure
In order to use flex and layout that defined in Material, we need to add a bug CSS file into our project, that defined CSS classes for flex
and layout
.
You can find the CSS file content here.
So let's copy it's content and add it to client/imports/material-layout.scss
.
Now let's add it to the main SCSS file imports:
22
23
24
25
26
width: 450px;
height: 450px;
}
@import "./imports/material-layout";
And let's add another CSS class missing:
23
24
25
26
27
28
29
30
height: 450px;
}
.md-whiteframe-z2 {
box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12);
}
@import "./imports/material-layout";
The import of this CSS file is temporary, and we will need to use it only because
angular2-material
is still in beta and not implemented all the features.
In this chapter we replaced Boostrap4 with Angular2-Material, and updated all the view and layout to match the component we got from it.
We also learnt how to use Meteor's Accounts API and how to implement authentication view and components, and how to connect them to our app.