In this step we will consider switching from Twitter Bootstrap to angular-material.
Angular-material is an Angular 1 implementation of Google's Material Design specifications. Material Design is a mobile-first design language used in many new Google's applications, especially on the Android platform.
To start, first we have to remove bootstrap from our application. Type in the console:
meteor npm uninstall bootstrap --save
We should also remove dependency from the main.js file:
1
2
3
4
import angular from 'angular';
import { Meteor } from 'meteor/meteor';
Now we have to add the angular-material Meteor package:
meteor npm install angular-aria angular-animate angular-material --save
We still need file with styles:
6
7
8
9
10
11
12
<title>Socially</title>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDbphq9crcdpecbseKX3Yx2LPxMRqWK-rc"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.0.7/angular-material.min.css">
</head>
<body>
<socially></socially>
Next, we want to inject the angular-material module into our Angular 1 application.
1
2
3
4
5
6
15
16
17
18
19
20
21
import angular from 'angular';
import angularMeteor from 'angular-meteor';
import ngMaterial from 'angular-material';
import uiRouter from 'angular-ui-router';
import template from './socially.html';
...some lines skipped...
// create a module
export default angular.module(name, [
angularMeteor,
ngMaterial,
uiRouter,
PartiesList,
PartyDetails,
That's it, now we can use angular-material
in our application layout.
Let's add Material Design Icons
and Ionic Icons
to Socially:
meteor add pagebakers:ionicons
meteor add planettraining:material-design-icons
We have to define the $mdIconProvider
.
29
30
31
32
33
34
35
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
.config(config)
.run(run);
function config($locationProvider, $urlRouterProvider, $qProvider, $mdIconProvider) {
'ngInject';
$locationProvider.html5Mode(true);
...some lines skipped...
$urlRouterProvider.otherwise('/parties');
$qProvider.errorOnUnhandledRejections(false);
const iconPath = '/packages/planettraining_material-design-icons/bower_components/material-design-icons/sprites/svg-sprite/';
$mdIconProvider
.iconSet('social',
iconPath + 'svg-sprite-social.svg')
.iconSet('action',
iconPath + 'svg-sprite-action.svg')
.iconSet('communication',
iconPath + 'svg-sprite-communication.svg')
.iconSet('content',
iconPath + 'svg-sprite-content.svg')
.iconSet('toggle',
iconPath + 'svg-sprite-toggle.svg')
.iconSet('navigation',
iconPath + 'svg-sprite-navigation.svg')
.iconSet('image',
iconPath + 'svg-sprite-image.svg');
}
function run($rootScope, $state) {
You don't have to define all these icon sets. You just need to define those you need to use. You can see a full list of available icons here.
This is the example from PartyRemove
component:
<md-icon md-svg-icon="content:ic_clear_24px"></md-icon>
In the md-svg-icon
attribute we used <iconset>:<icon_name>
in our case content:ic_clear_24px
.
You can read more about it by clicking here
Great! So now in order get rid of all the bootstrap change we make, we need to remove some and modify some CSS and LESS.
Angular-material uses declarative syntax, i.e. directives, to utilize Material Design elements in HTML documents.
First we want to change our main component which is Socially
1
2
3
<navigation></navigation>
<div ui-view=""></div>
Use md-toolbar
in Navigation:
1
2
3
4
5
6
7
8
9
10
<md-toolbar>
<div class="md-toolbar-tools">
<h2>
<span ui-sref="parties">
Socially
</span>
</h2>
<login-buttons></login-buttons>
</div>
</md-toolbar>
1
2
3
4
5
navigation {
login-buttons {
margin-left: 15px;
}
}
You can see we use layout="column"
in the first div
tag, which tells angular-material to lay all inner layers vertically.
Element layout flex grid is very simple and intuitive in angular-material
and you can read all about it here.
Later on, we use layout-gt-sm="row"
which overrides column
setting on screens greater than 960px wide.
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
<div layout="column" layout-padding>
<party-add ng-show="partiesList.isLoggedIn"></party-add>
<div ng-hide="partiesList.isLoggedIn">
<i>Log in to create a party!</i>
</div>
<div flex>
<h2 class="md-display-1">List of the parties:</h2>
</div>
<div flex layout="row" layout-padding>
<md-input-container>
<label>Search</label>
<input ng-model="partiesList.searchText">
</md-input-container>
<parties-sort on-change="partiesList.sortChanged(sort)" property="name" order="1"></parties-sort>
</div>
<div layout="column" layout-gt-sm="row">
<div flex="50">
<md-card dir-paginate="party in partiesList.parties | itemsPerPage: partiesList.perPage" total-items="partiesList.partiesCount">
<md-card-title>
<md-card-title-text>
<span class="md-headline" ui-sref="partyDetails({ partyId: party._id })">
{{party.name}}
<party-remove party="party"></party-remove>
</span>
<span class="md-subhead">{{party.description}}</span>
</md-card-title-text>
</md-card-title>
<md-card-content>
<party-rsvps-list rsvps="party.rsvps"></party-rsvps-list>
<party-unanswered party="party" ng-if="!party.public"></party-unanswered>
<div ng-if="party.public">
Everyone is invited
</div>
<party-creator party="party"></party-creator>
</md-card-content>
<md-card-actions>
<party-rsvp party="party" ng-show="partiesList.isLoggedIn"></party-rsvp>
<div ng-hide="partiesList.isLoggedIn">
<i>Sign in to RSVP for this party.</i>
</div>
</md-card-actions>
</md-card>
<dir-pagination-controls on-page-change="partiesList.pageChanged(newPageNumber)"></dir-pagination-controls>
</div>
<div flex="50">
<parties-map parties="partiesList.parties"></parties-map>
</div>
</div>
1
2
3
4
5
parties-list {
[ui-sref] {
cursor: pointer;
}
}
Remove the heading:
1
2
3
<div class="angular-google-map-container">
<ui-gmap-google-map center="partiesMap.map.center" zoom="partiesMap.map.zoom">
<ui-gmap-markers models="partiesMap.parties" coords="'location'" fit="true" idkey="'_id'" doRebuildAll="true"></ui-gmap-markers>
1
2
3
4
5
6
parties-map {
display: block;
padding: 10px;
.angular-google-map-container {
width: 100%;
3
4
5
6
7
cursor: pointer;
}
}
@import "../partiesMap/partiesMap.less";
Let's use md-input-container
combined with md-select
:
1
2
3
4
5
6
7
8
9
10
<md-input-container>
<md-select ng-model="partiesSort.order" ng-change="partiesSort.changed()">
<md-option value="1">
Ascending
</md-option>
<md-option value="-1">
Descending
</md-option>
</md-select>
</md-input-container>
We won't be using partyAdd.less
any longer, so it can be removed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div layout="column" layout-padding>
<md-input-container>
<label>
Party Name:
</label>
<input type="text" ng-model="partyAdd.party.name"/>
</md-input-container>
<md-input-container>
<label>
Description:
</label>
<input type="text" ng-model="partyAdd.party.description"/>
</md-input-container>
<div flex>
<md-checkbox ng-model="partyAdd.party.public" aria-label="Public Party?">
Public Party?
</md-checkbox>
</div>
<div flex>
<md-button ng-click="partyAdd.submit()" class="md-raised">Add Party!</md-button>
</div>
</div>
We can now remove partyCreator.less since we no longer need it.
1
2
3
4
5
<p>
<small>
Posted by {{ partyCreator.creator | displayNameFilter }}
</small>
</p>
Let's use clear
icon from content
set:
1
<md-icon md-svg-icon="content:ic_clear_24px" ng-click="partyRemove.remove()"></md-icon>
Move component to the right:
1
2
3
party-remove {
float: right;
}
5
6
7
8
}
@import "../partiesMap/partiesMap.less";
@import "../partyRemove/partyRemove.less";
Since we using only the angular-material directives you can remove .less file of PartyRsvp component:
1
2
3
4
5
<div layout="row" layout-align="end center">
<md-button ng-click="partyRsvp.yes()" ng-class="{'md-primary' : partyRsvp.isYes()}">I'm going!</md-button>
<md-button ng-click="partyRsvp.maybe()" ng-class="{'md-primary' : partyRsvp.isMaybe()}">Maybe</md-button>
<md-button ng-click="partyRsvp.no()" ng-class="{'md-primary' : partyRsvp.isNo()}">No</md-button>
</div>
Thanks to the angular-material we no longer need partyUninvited.less:
1
2
3
4
5
6
7
8
9
10
11
12
<h4 class="md-headline">
Users to invite:
</h4>
<md-list>
<md-list-item ng-repeat="user in partyUninvited.users | uninvitedFilter:partyUninvited.party" ng-click="partyUninvited.invite(user)">
<p>{{ user | displayNameFilter }}</p>
</md-list-item>
<md-list-item ng-if="(partyUninvited.users | uninvitedFilter:partyUninvited.party).length <= 0">
Everyone are already invited.
</md-list-item>
</md-list>
Let's import styles in PartiesList:
6
7
8
9
@import "../partiesMap/partiesMap.less";
@import "../partyRemove/partyRemove.less";
@import "../partyRsvpsList/partyRsvpsList.less";
Add a little padding from each side:
1
2
3
4
5
6
party-map {
display: block;
padding: 10px;
.angular-google-map-container {
width: 100%;
As you can see, we used md-input-container
in similar way as we did with PartyAdd:
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
<div layout="column" layout-padding>
<div layout="column" layout-gt-sm="row" layout-padding>
<form flex="50" layout="column">
<md-input-container>
<label>Party name</label>
<input ng-model="partyDetails.party.name" ng-disabled="!partyDetails.isOwner">
</md-input-container>
<md-input-container>
<label>Description</label>
<input ng-model="partyDetails.party.description" ng-disabled="!partyDetails.isOwner">
</md-input-container>
<div>
<md-checkbox ng-model="partyDetails.party.public" ng-disabled="!partyDetails.isOwner" aria-label="Public Party?">
Public Party?
</md-checkbox>
</div>
<div>
<md-button ng-click="partyDetails.save()" class="md-primary md-raised">Save</md-button>
</div>
</form>
<party-map flex="50" location="partyDetails.party.location"></party-map>
</div>
<party-uninvited flex party="partyDetails.party" ng-show="partyDetails.canInvite()"></party-uninvited>
</div>
Make partyDetails.less
to look like this:
1
@import "../partyMap/partyMap.less";
It would be great to move PartyAdd outside PartiesList. It would be even greater to make modal window for it.
We need to create some sort of modal window trigger:
1
2
3
<md-button class="md-fab" aria-label="Add new party" ng-click="partyAddButton.open($event)">
<md-icon md-svg-icon="content:ic_add_24px"></md-icon>
</md-button>
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
import angular from 'angular';
import angularMeteor from 'angular-meteor';
import buttonTemplate from './partyAddButton.html';
import modalTemplate from './partyAddModal.html';
import { name as PartyAdd } from '../partyAdd/partyAdd';
class PartyAddButton {
constructor($mdDialog, $mdMedia) {
'ngInject';
this.$mdDialog = $mdDialog;
this.$mdMedia = $mdMedia
}
open(event) {
this.$mdDialog.show({
controller($mdDialog) {
'ngInject';
this.close = () => {
$mdDialog.hide();
}
},
controllerAs: 'partyAddModal',
template: modalTemplate,
targetEvent: event,
parent: angular.element(document.body),
clickOutsideToClose: true,
fullscreen: this.$mdMedia('sm') || this.$mdMedia('xs')
});
}
}
const name = 'partyAddButton';
// create a module
export default angular.module(name, [
angularMeteor,
PartyAdd
]).component(name, {
template: buttonTemplate,
controllerAs: name,
controller: PartyAddButton
});
What we did there?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<md-dialog aria-label="New party" ng-cloak>
<md-toolbar>
<div class="md-toolbar-tools">
<h2>New party</h2>
<span flex></span>
<md-button ng-click="partyAddModal.close()">
Close
</md-button>
</div>
</md-toolbar>
<md-dialog-content>
<div class="md-dialog-content">
<party-add done="partyAddModal.close()"></party-add>
</div>
</md-dialog-content>
</md-dialog>
1
2
3
4
5
party-add-button {
position: fixed;
right: 15px;
bottom: 15px;
}
done
directive on PartyAdd
component and for now it does nothing.
It would be an expression binding which invokes after a new party has been added.
14
15
16
17
18
19
20
21
22
23
24
34
35
36
37
38
39
40
41
42
submit() {
this.party.owner = Meteor.userId();
Parties.insert(this.party);
if(this.done) {
this.done();
}
this.reset();
}
...some lines skipped...
angularMeteor
]).component(name, {
template,
bindings: {
done: '&?'
},
controllerAs: name,
controller: PartyAdd
});
Great! Our new component is now working and cooperating with PartyAdd component.
Let's implement it into PartiesList
:
9
10
11
12
13
14
15
77
78
79
80
81
82
83
import { Parties } from '../../../api/parties';
import { name as PartiesSort } from '../partiesSort/partiesSort';
import { name as PartiesMap } from '../partiesMap/partiesMap';
import { name as PartyAddButton } from '../partyAddButton/partyAddButton';
import { name as PartyRemove } from '../partyRemove/partyRemove';
import { name as PartyCreator } from '../partyCreator/partyCreator';
import { name as PartyRsvp } from '../partyRsvp/partyRsvp';
...some lines skipped...
utilsPagination,
PartiesSort,
PartiesMap,
PartyAddButton,
PartyRemove,
PartyCreator,
PartyRsvp,
1
2
3
4
5
<div layout="column" layout-padding>
<party-add-button ng-show="partiesList.isLoggedIn"></party-add-button>
<div flex>
<h2 class="md-display-1">List of the parties:</h2>
6
7
8
9
10
@import "../partiesMap/partiesMap.less";
@import "../partyRemove/partyRemove.less";
@import "../partyAddButton/partyAddButton.less";
@import "../partyRsvpsList/partyRsvpsList.less";
Now try to click on a button displayed in the right bottom corner of the screen. You should see opened modal window with PartyAdd component!
Our next step will replace the login-buttons which is a simple and non-styled login/register component - we will add our custom authentication component with custom style.
First, let's create Auth
component:
1
2
3
4
5
6
<div layout="row">
<md-button flex ui-sref="login" ng-hide="auth.isLoggedIn">Login</md-button>
<md-button flex ui-sref="register" ng-hide="auth.isLoggedIn">Sign up</md-button>
<md-button flex ng-click="auth.logout()" ng-show="auth.isLoggedIn">Logout</md-button>
<div ng-show="auth.isLoggedIn">{{ auth.currentUser | displayNameFilter }}</div>
</div>
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 angular from 'angular';
import angularMeteor from 'angular-meteor';
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import template from './auth.html';
import { name as DisplayNameFilter } from '../../filters/displayNameFilter';
const name = 'auth';
class Auth {
constructor($scope, $reactive) {
'ngInject';
$reactive(this).attach($scope);
this.helpers({
isLoggedIn() {
return !!Meteor.userId();
},
currentUser() {
return Meteor.user();
}
});
}
logout() {
Accounts.logout();
}
}
// create a module
export default angular.module(name, [
angularMeteor,
DisplayNameFilter
]).component(name, {
template,
controllerAs: name,
controller: Auth
});
As you can see, we're going to create components for few new states.
First state is a page with Login
component.
In this component we use Meteor's accounts, and use the Accounts API to login our user with email and password.
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
<md-content layout="row" layout-align="center start" layout-fill layout-margin>
<md-whiteframe layout="column" flex flex-md="50" flex-lg="50" flex-gt-lg="33" class="md-whiteframe-z2" layout-fill>
<md-toolbar class="md-primary md-tall" layout="column" layout-align="end" layout-fill>
<div layout="row" class="md-toolbar-tools md-toolbar-tools-bottom">
<h3 class="md-display-1">
Sign in
</h3>
</div>
</md-toolbar>
<div layout="column" layout-fill layout-margin layout-padding>
<div layout="row" layout-fill layout-margin>
<p class="md-body-2">
Use existing account</p>
</div>
<div layout="row" layout-fill layout-margin layout-padding layout-wrap>
<md-button class="md-raised">
<i class="icon ion-social-google" style="color: #DC4A38; font-size: 24px;"></i>
<span>
Google</span>
</md-button>
<md-button class="md-raised">
<i class="icon ion-social-facebook" style="color: #3F62B4; font-size: 24px;"></i>
<span>Facebook
</span>
</md-button>
<md-button class="md-raised">
<i class="icon ion-social-twitter" style="color: #27AAE2; font-size: 24px;"></i>
<span>Twitter
</span>
</md-button>
</div>
<md-divider class="inset"></md-divider>
<div layout="row" layout-fill layout-margin>
<p class="md-body-2">
Sign in with your email</p>
</div>
<form name="loginForm" layout="column" layout-fill layout-padding layout-margin>
<md-input-container>
<label>
</label>
<input type="text" ng-model="login.credentials.email" aria-label="email" required/>
</md-input-container>
<md-input-container>
<label>
Password
</label>
<input type="password" ng-model="login.credentials.password" aria-label="password" required/>
</md-input-container>
<div layout="row" layout-align="space-between center">
<a class="md-button" href="/password">Forgot password?</a>
<md-button class="md-raised md-primary" ng-click="login.login()" aria-label="login" ng-disabled="login.loginForm.$invalid()">Sign In
</md-button>
</div>
</form>
<md-toolbar ng-show="login.error" class="md-warn" layout="row" layout-fill layout-padding layout-margin>
<p class="md-body-1">{{ login.error }}</p>
</md-toolbar>
<md-divider></md-divider>
<div layout="row" layout-align="center">
<a class="md-button" href="/register">Need an account?</a>
</div>
</div>
</md-whiteframe>
</md-content>
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
import angular from 'angular';
import angularMeteor from 'angular-meteor';
import uiRouter from 'angular-ui-router';
import { Meteor } from 'meteor/meteor';
import template from './login.html';
import { name as Register } from '../register/register';
class Login {
constructor($scope, $reactive, $state) {
'ngInject';
this.$state = $state;
$reactive(this).attach($scope);
this.credentials = {
email: '',
password: ''
};
this.error = '';
}
login() {
Meteor.loginWithPassword(this.credentials.email, this.credentials.password,
this.$bindToContext((err) => {
if (err) {
this.error = err;
} else {
this.$state.go('parties');
}
})
);
}
}
const name = 'login';
// create a module
export default angular.module(name, [
angularMeteor,
uiRouter
])
.component(name, {
template,
controllerAs: name,
controller: Login
})
.config(config);
function config($stateProvider) {
'ngInject';
$stateProvider.state('login', {
url: '/login',
template: '<login></login>'
});
}
6
7
8
9
10
11
12
34
35
36
37
38
39
40
41
import template from './auth.html';
import { name as DisplayNameFilter } from '../../filters/displayNameFilter';
import { name as Login } from '../login/login';
const name = 'auth';
...some lines skipped...
// create a module
export default angular.module(name, [
angularMeteor,
DisplayNameFilter,
Login
]).component(name, {
template,
controllerAs: name,
In Register
component we use Meteor's accounts, and use the Accounts API to add a new user.
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
<md-content layout="row" layout-align="center start" layout-fill layout-margin>
<md-whiteframe layout="column" flex flex-md="50" flex-lg="50" flex-gt-lg="33" class="md-whiteframe-z2" layout-fill>
<md-toolbar class="md-primary md-tall" layout="column" layout-align="end" layout-fill>
<div layout="row" class="md-toolbar-tools md-toolbar-tools-bottom">
<h3 class="md-display-1">
Register a new account</h3>
</div>
</md-toolbar>
<div layout="column" layout-fill layout-margin layout-padding>
<div layout="row" layout-fill layout-margin>
<p class="md-body-2">Use your email?</p>
</div>
<form name="registerForm" layout="column" layout-fill layout-padding layout-margin>
<md-input-container >
<label>
</label>
<input type="text" ng-model="register.credentials.email" placeholder="email" aria-label="email" required/>
</md-input-container>
<md-input-container >
<label>
Password
</label>
<input type="password" ng-model="register.credentials.password" placeholder="password" aria-label="password" required/>
</md-input-container>
<div layout="row" layout-align="end center">
<md-button class="md-raised md-primary" ng-click="register.register()" aria-label="login" ng-disabled="register.registerForm.$invalid()">Register</md-button>
</div>
</form>
<md-divider class="inset"></md-divider>
<div layout="row" layout-fill layout-margin>
<p class="md-body-2">
Want to use an existing account?
</p>
</div>
<div layout="row" layout-fill layout-margin layout-padding layout-wrap>
<md-button class="md-raised">
<md-icon md-svg-icon="social:ic_google_24px" style="color: #DC4A38;"></md-icon>
<span>
Google</span>
</md-button>
<md-button class="md-raised">
<md-icon md-svg-icon="social:ic_facebook_24px" style="color: #3F62B4;"></md-icon>
<span>Facebook
</span>
</md-button>
<md-button class="md-raised">
<md-icon md-svg-icon="social:ic_twitter_24px" style="color: #27AAE2;"></md-icon>
<span>Twitter
</span>
</md-button>
</div>
<md-toolbar ng-show="register.error" class="md-warn" layout="row" layout-fill layout-padding layout-margin>
<p class="md-body-1">{{ register.error }}</p>
</md-toolbar>
<md-divider></md-divider>
<div layout="row" layout-align="center">
<a class="md-button" href="/login">Already a user?</a>
</div>
</div>
</md-whiteframe>
</md-content>
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
import angular from 'angular';
import angularMeteor from 'angular-meteor';
import uiRouter from 'angular-ui-router';
import { Accounts } from 'meteor/accounts-base';
import template from './register.html';
class Register {
constructor($scope, $reactive, $state) {
'ngInject';
this.$state = $state;
$reactive(this).attach($scope);
this.credentials = {
email: '',
password: ''
};
this.error = '';
}
register() {
Accounts.createUser(this.credentials,
this.$bindToContext((err) => {
if (err) {
this.error = err;
} else {
this.$state.go('parties');
}
})
);
}
}
const name = 'register';
// create a module
export default angular.module(name, [
angularMeteor,
uiRouter
])
.component(name, {
template,
controllerAs: name,
controller: Register
})
.config(config);
function config($stateProvider) {
'ngInject';
$stateProvider.state('register', {
url: '/register',
template: '<register></register>'
});
}
7
8
9
10
11
12
13
36
37
38
39
40
41
42
43
import template from './auth.html';
import { name as DisplayNameFilter } from '../../filters/displayNameFilter';
import { name as Login } from '../login/login';
import { name as Register } from '../register/register';
const name = 'auth';
...some lines skipped...
export default angular.module(name, [
angularMeteor,
DisplayNameFilter,
Login,
Register
]).component(name, {
template,
controllerAs: name,
We also have "Recover" button in the login page, so let's create a component that handles that, and call it Password
:
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
<md-content layout="row" layout-align="center start" layout-fill layout-margin>
<md-whiteframe layout="column" flex flex-md="50" flex-lg="50" flex-gt-lg="33" class="md-whiteframe-z2" layout-fill>
<md-toolbar class="md-primary md-tall" layout="column" layout-align="end" layout-fill>
<div layout="row" class="md-toolbar-tools md-toolbar-tools-bottom">
<h3 class="md-display-1"> Reset Password</h3>
</div>
</md-toolbar>
<div layout="column" layout-fill layout-margin layout-padding>
<div layout="row" layout-fill layout-margin>
<p class="md-body-2">Enter your email so we can send you a reset link</p>
</div>
<form name="resetForm" layout="column" layout-fill layout-padding layout-margin>
<md-input-container>
<label> Email </label>
<input type="text" ng-model="password.credentials.email" placeholder="email" aria-label="email" required/>
</md-input-container>
<div layout="row" layout-align="end center">
<md-button class="md-raised md-primary" ng-click="password.reset()" aria-label="reset"
ng-disabled="password.resetForm.$invalid()">Send Email
</md-button>
</div>
</form>
<md-toolbar ng-show="password.error" class="md-warn" layout="row" layout-fill layout-padding layout-margin>
<p class="md-body-1">{{ password.error }}</p>
</md-toolbar>
<md-divider></md-divider>
<div layout="row" layout-align="center">
<a class="md-button" href="/login">Sign in</a>
</div>
</div>
</md-whiteframe>
</md-content>
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
import angular from 'angular';
import angularMeteor from 'angular-meteor';
import uiRouter from 'angular-ui-router';
import { Accounts } from 'meteor/accounts-base';
import template from './password.html';
class Register {
constructor($scope, $reactive, $state) {
'ngInject';
this.$state = $state;
$reactive(this).attach($scope);
this.credentials = {
email: ''
};
this.error = '';
}
reset() {
Accounts.forgotPassword(this.credentials, this.$bindToContext((err) => {
if (err) {
this.error = err;
} else {
this.$state.go('parties');
}
}));
}
}
const name = 'password';
// create a module
export default angular.module(name, [
angularMeteor,
uiRouter
])
.component(name, {
template,
controllerAs: name,
controller: Register
})
.config(config);
function config($stateProvider) {
'ngInject';
$stateProvider.state('password', {
url: '/password',
template: '<password></password>'
});
}
8
9
10
11
12
13
14
38
39
40
41
42
43
44
45
import { name as DisplayNameFilter } from '../../filters/displayNameFilter';
import { name as Login } from '../login/login';
import { name as Register } from '../register/register';
import { name as Password } from '../password/password';
const name = 'auth';
...some lines skipped...
angularMeteor,
DisplayNameFilter,
Login,
Register,
Password
]).component(name, {
template,
controllerAs: name,
Since every component is ready, we can now implement Auth into Socially:
7
8
9
10
11
12
13
21
22
23
24
25
26
27
import { name as PartiesList } from '../partiesList/partiesList';
import { name as PartyDetails } from '../partyDetails/partyDetails';
import { name as Navigation } from '../navigation/navigation';
import { name as Auth } from '../auth/auth';
class Socially {}
...some lines skipped...
PartiesList,
PartyDetails,
Navigation,
Auth,
'accounts.ui'
]).component(name, {
template,
5
6
7
8
9
10
11
Socially
</span>
</h2>
<span flex></span>
<auth></auth>
</div>
</md-toolbar>
Inside the md-toolbar
you see we used
<span flex></span>
element which is actually a separator blank element which is used to fill all the available blank space between the first and third element in the toolbar.
We can now remove navigation.less
, which we don't need any longer:
2
3
4
5
6
display: block;
}
@import "../partiesList/partiesList.less";
@import "../partyDetails/partyDetails.less";
That's it! we just implemented our own authentication components using Meteor's Accounts API and angular-material!
In this chapter we two main things:
I hope one of you will create an accounts-ui package based on that code and will save us all tons of code!