An introduction to Angular
May 4, 2015 angular authentication
DISCLAIMER: THIS ARTICLE IS REALLY OLD, NEEDS A PROPER UPDATE
Ready for Angular v2? You bet!
For this demo, we are going to code an application which requests german words. The word will also come with its translation if the user is authenticated.
First, grab the KOA backend from here, then:
$ npm install
$ node server.js
NOTE: You need node 0.11+ or io.js to work with koa. And if it is node, you need to use the –harmony flag like:
$ node --harmony server.js
Leave it running and clone this repo. Here we are going to work on the before
folder. To use it:
$ npm install
$ npm start
I got this boilerplate from my friend Pawel and I updated thanks to mgonto and gdi2290.
Let me explain it a bit:
Since Angular 2
is still not final, it needs a lot of boilerplate to be setup, and this skeleton do it for you.
On one hand, we have the gulpfile
where apart from the classic tasks to process javascript and css, we have tasks to build both angular and its router from the sources.
On the other hand, we have the index.html
file where we load all the needed libraries for Angular 2
. As I said before, since Angular 2
is still not final, we have to load a lot of libraries to make it work, but worry not, on the final version we won’t need to do that.
Another interesting thing here is System
. System
is the module loader of ES6
and it is the one which will start our application. Yay, no more hundreds of script tags!
Also, there is no more ng-app
:)
Alright! System
is going to load index
which is supposed to bootstrap our app, right? How do we do that? To bootstrap an Angular 2
application we need to use the bootstrap
method passing our main component. Component? Yeah, we will see in a bit :)
bootstrap(OurMainComponent);
For our app, we will have an App
component so to bootstrap our application, we can do:
File: index.js
import { bootstrap } from 'angular2/angular2';
import { App } from './app/app';
bootstrap(App);
We just need to import our App
component and the bootstrap method to then use it to bootstrap the app. Another thing we need to do for our app, is to load the router. The router is external to angular 2, so we need to load it as a dependency. As today (2015-05-04) the router is not exporting the needed injectable to make this easy, so we need to construct a new instance of the router and inject it:
File: index.js
import { bootstrap } from 'angular2/angular2';
import { RootRouter } from 'angular2/src/router/router';
import { Pipeline } from 'angular2/src/router/pipeline';
import { bind } from 'angular2/di';
import { Router } from 'angular2/router';
import { App } from './app/app';
bootstrap(App, [
bind(Router).toValue(new RootRouter(new Pipeline()))
]);
We loaded all the needed dependencies to construct the router and also the bind
service to create a Binding for the router. Hopefully this will be fixed really soon.
Okay, let’s code the App component. But what’s a component? A component is a just a class which can be used to represent a page like home
, login
, users
… or even used to create a directive
like datepicker
, tabs
, etc.
File: app/app.js
export class App {
}
As I said, the component is just a class (we export it to be able to import it from other files like we did in index
). So far it is not doing anything, so let fix that.
In Angular 2
we can use annotations. Think about annotations as a way to add metadata to our classes. Let’s go step by step. First import the two annotations we need:
File: app/app.js
import {View, Component} from 'angular2/angular2';
Then we just need to use them. Component
is an annotation to add metadata about the component itself, that includes its selector, or what services we need to inject. On the other hand, the View
annotation is used for the HTML templates. Here we can specify the template we want to use with the component, if we need to use directives in it, etc. We can have more than one View
annotation (mobile view, desktop view, etc).
Let’s use them:
File: app/app.js
@Component({
selector: 'words-app'
})
@View({
template: `<h1>Hello angular 2</h1>`
})
export class App {
}
For Component
we specified that the selector for this component will be words-app
(look mum, no more camelCase
vs snake-case
like Angular 1
), that means that to use this component, we just need to drop a <words-app></words-app>
somewhere!
For this View
we created a simple template (notice the quotes).
NOTE: Don’t put semicolons after each annotation, that will make Angular 2
cry :)
So you said we can drop that selector somewhere? Yeah, let’s modify the index.html
:
File: index.html
<body>
<div class="container">
<words-app>
Loading...
</words-app>
</div>
</body>
Ok, here we used our new component. Let’s go to localhost:3000
So far so good, isn’t it? Inside App
we are going to configure the router that we got injected from the bootstrap
function:
File: app/app.js
export class App {
constructor(router: Router) {
}
}
The class constructor will receive a router
parameter of the type Router
. Let’s use it:
File: app/app.js
export class App {
constructor(router: Router) {
router
.config('/home', Home)
.then(() => router.navigate('/home'));
}
}
The router
has a config
method where we pass the path
and what component
to use for that path. We chain the promise it returns to immediately navigate to that /home
route.
Soon we will be able to configure the routes of each component as an annotation, but for the time being, we will use this way of configuring the router.
When we load a new route, where do we put the component? We need an ng-view
which is called router-outlet
in this new router.
Let’s change our View
annotation like:
File: app/app.js
@View({
template: `<router-outlet></router-outlet>`,
directives: [RouterOutlet]
})
There is something important here: If our template uses a directive/component, we need to import it explicitly and that is the case of RouterOutlet
. Notice that on the directives
array we put the component object we are importing. Talking about imports… we need to add a few. Our imports are now like:
File: app/app.js
import {View, Component} from 'angular2/angular2';
import {Router, RouterOutlet} from 'angular2/router';
import {Home} from '../home/home';
We loaded the Router
, the RouterOutlet
component and also the Home
component.
Now our app will navigate directly to that Home
component. Let’s create it:
File: home/home.js
import {View, Component} from 'angular2/angular2';
@Component({
selector: 'home'
})
@View({
templateUrl: 'home/home.html'
})
export class Home {
}
This time instead of embedding our template directly on the annotation, we will create an external file for it where we put:
File: home/home.html
<h1>This is home</h1>
Before running our app, let’s add bootstrap to our index.css
:
File: index.css
@import 'bootstrap';
Now we have something like:
Let’s do something real, shall we? On this home component, we want to grab a new word every time we click a button. To abstract Home
from requests and stuff, let’s create a service for that, called Words
:
File: services/words.js
export class Words {
getWord() {
var jwt = localStorage.getItem('jwt');
return fetch('http://localhost:3001/api/random-word', {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'bearer ' + jwt
}
})
.then((response) => {
return response.text();
})
.then((text) => {
return JSON.parse(text);
})
}
}
A service in Angular 2
is just a class and for this one, we just need a method to grab a word. As today, Angular 2
doesn’t have anything like $http
so we are going to use a library called fetch
.
With fetch
we make a request to our backend, passing the JWT
token if there is any, we extract the text from the response and we parse it so the next time we create a new .then
we will receive the final object.
Let’s import this service in Home
:
File: home/home.js
import {Words} from '../services/words';
Now we need to tell our component that we want to inject the service:
File: home/home.js
@Component({
selector: 'home',
injectables: [Words]
})
Finally, we receive a new instance of the service in the constructor:
File: home/home.js
export class Home {
constructor(words: Words) {
this.words = words;
}
}
Like with the router, we receive a words
instance of the type Words
.
With our service in place, let’s code the template:
File: home/home.html
<div class="jumbotron centered">
<h1>German words demo!</h1>
<p>Click the button below to get a random German word with its translation:</p>
<p><a class="btn btn-primary" role="button" (click)="getRandomWord()">Give me a word!</a></p>
<div *if="word">
<pre>Word: {{word.german}}</pre>
</div>
</div>
There is a couple of new stuff in Angular 2
. Instead of ng-click
we use (click)
, the parenthesis means that it is an event (click event). On the other hand, we have that *if
which is our classic ng-if
. The star means that it is a template, basically a shorter version of doing:
<template [if]="word">
Is that if
a directive? Yes, and what we said about using directives inside our templates? That we need to import them:
File: home/home.js
import {View, Component, If} from 'angular2/angular2';
And then:
File: home/home.js
@View({
templateUrl: 'home/home.html',
directives: [If]
})
Next, we need that button working so we can show the words:
File: home/home.js
getRandomWord() {
this.words.getWord().then((response) => {
this.word = response;
});
}
If we now try the app, we can see something like:
Alright, it is getting shape!
Let’s move to the authentication. A service you said? Right on!
File: services/auth.js
export class Auth {
constructor() {
this.token = localStorage.getItem('jwt');
this.user = this.token && jwt_decode(this.token);
}
isAuth() {
return !!this.token;
}
getUser() {
return this.user;
}
login(username, password) {
return fetch('http://localhost:3001/sessions/create', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username, password
})
})
.then((response) => {
return response.text();
})
.then((text) => {
this.token = JSON.parse(text).id_token;
localStorage.setItem('jwt', this.token);
});
}
logout() {
localStorage.removeItem('jwt');
this.token = null;
this.user = null;
}
}
We store two items, the token and the decoded token, then we have a method to login and a method to logout. Nothing really special. Both localStorage
and jwt_decode
are globals so we don’t need to import that. Again, we are using fetch
to do the request to the backend.
On the other hand, when we talk about login and stuff, we need a way to actually login, right? Let’s create a component for login:
File: login/login.js
import {Component, View} from 'angular2/angular2';
import {Router} from 'angular2/router';
import {Auth} from '../services/auth';
@Component({
selector: 'login',
injectables: [Auth]
})
@View({
templateUrl: 'login/login.html'
})
export class Login {
constructor(router: Router, auth: Auth) {
this.router = router;
this.auth = auth;
}
login(event, username, password) {
event.preventDefault();
this.auth.login(username, password).then(() => {
this.router.parent.navigate('/home');
})
.catch((error) => {
alert(error);
});
}
}
Like the other components, we have our Component
and View
annotations and we also specified that the new Auth
service is going to be injected into the component.
This component only have one method were we use the Auth
service to login and set the jwt
token in the localStorage
. If we succeed, we just navigate to the /home
page.
For the template, we have:
File: login/login.html
<div class="login jumbotron center-block">
<h1>Login</h1>
<form role="form" (submit)="login($event, username.value, password.value)">
<div class="form-group">
<label for="username">Username</label>
<input type="text" #username class="form-control" id="username" placeholder="Username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" #password class="form-control" id="password" placeholder="Password">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
Notice how we have stuff like #username
instead of ng-model="username"
. That is how Angular 2
binds our stuff.
As a last step, let’s modify the css a bit because the form is a bit wide:
File: login/login.css
.login {
width: 40%;
}
We also need to import this .css
file:
File: index.css
@import 'bootstrap';
@import './src/login/login.css';
Having the login in place, we need a link for it in our Home
component:
File: home/home.html
<div class="jumbotron centered">
<h1>German words demo!</h1>
<p>Click the button below to get a random German word with its translation:</p>
<p><a class="btn btn-primary" role="button" (click)="getRandomWord()">Give me a word!</a></p>
<div *if="word">
<pre>Word: {{word.german}}</pre>
</div>
</div>
<div *if="isAuth">
<p>Welcome back {{user.username}}</p>
<a href="#" (click)="logout($event)">Logout</a>
</div>
<div *if="!isAuth">
<a href="#" (click)="login($event)">Login</a>
</div>
So having a flag called isAuth
, we will switch between two divs.
We will need the Auth
service here as well, so let’s import it:
File: home/home.js
import {Auth} from '../services/auth';
@Component({
selector: 'home',
injectables: [Words, Auth]
})
Then we need a login
and logout
methods, the isAuth
flag and also a reference to the user. Our component is now like:
File: home/home.js
export class Home {
constructor(router: Router, words: Words, auth: Auth) {
this.router = router;
this.auth = auth;
this.words = words;
this.isAuth = auth.isAuth();
if (this.isAuth) {
this.user = this.auth.getUser();
}
}
getRandomWord() {
this.words.getWord().then((response) => {
this.word = response;
});
}
login(event) {
event.preventDefault();
this.router.parent.navigate('/login');
}
logout(event) {
event.preventDefault();
this.auth.logout();
this.isAuth = false;
this.user = null;
}
}
In the near future, we can avoid that login
method thanks to the RouteLink
component which is basically like ui-sref
.
Alright, now we have the link to the login
page:
And if we click it, we… Oh wait, it is not working. Ah, we forgot to add the route for it back at app.js
:
File: app/app.js
import {Login} from '../login/login';
export class App {
constructor(router: Router) {
router
.config('/home', Home)
.then(() => router.config('/login', Login))
.then(() => router.navigate('/home'));
}
}
Now it works:
And if we login with the demo credentials (demo / 12345) we see:
That’s great!
The last step is showing the translation if we are logged in and that is easy to do! We are already grabbing the words from the server and now that the server sees that we are authenticated, it will send the translation as well. That means that we just need to update our template:
File: home/home.html
<div class="jumbotron centered">
<h1>German words demo!</h1>
<p>Click the button below to get a random German word with its translation:</p>
<p><a class="btn btn-primary" role="button" (click)="getRandomWord()">Give me a word!</a></p>
<div *if="word">
<pre>Word: {{word.german}}</pre>
<pre *if="isAuth">Translation: {{word.english}}</pre>
<p *if="!isAuth">Please login below to see the translation</p>
</div>
</div>
<div *if="isAuth">
<p>Welcome back {{user.username}}</p>
<a href="#" (click)="logout($event)">Logout</a>
</div>
<div *if="!isAuth">
<a href="#" (click)="login($event)">Login</a>
</div>
Now we have our app completed:
Oh wait, I forgot to log in:
I bet you can learn Angular 2
faster than the word on the image :)
Before closing this article and as a curiosity, you can import the Login
component in Home
, add it as a used directive in the View
component and then add <login></login>
at the bottom of the template. Doing that, you will attach the entire login form and its functionality to the Home
component, yay!
I want to thanks Matias Gontovnikas and PatrickJS for their demo angular2-authentication-sample and explanations which helped me a lot understanding the whole process.