THE AMAZING

   ANGULAR 2

ROUTER

slides.com/gerardsans | @gerardsans

Google Developer Expert

Master of Ceremonies

International Speaker

Angular 2 Trainer

Community Leader

800

500

Some days before

GDG DevFest London...

Angular 2

Features

  • Latest Web Standards
  • Great Developer Experience
  • Simple
  • Lightning fast
  • Works everywhere

TypeScript

ES6 (ES2015)

  • Classes, modules, arrow functions

TypeScript

  • Types, decorators, generics, interfaces
  • Great editor support

TypeScript IDE Support

Modern Tooling

Application Design

Requirements

  • Network resilience
  • Ask before navigating
  • Restrict access
  • Lazy loading

Components Tree

source: blog

/home

/users

/about

/users/34

Bootstrap

Bootstrapping

  • Angular Application instantiation
  • Root Module (AppModule) 
  • Global Dependencies
    • Router, Http​, Services
    • Global Values
    • Vendor dependencies

index.html

<!DOCTYPE html>
<html>
  <head>
    <!-- Polyfill(s) for older browsers -->
    <script src="https://unpkg.com/core-js/client/shim.min.js"></script>
    <script src="https://unpkg.com/zone.js@0.6.17?main=browser"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3"></script>
    <script src="https://unpkg.com/systemjs@0.19.27/dist/system.src.js"></script>
    <script src="systemjs.config.js"></script>
    <script>System.import('app');</script>
  </head>
  <body>
    <my-app>
      Loading...
    </my-app>
  </body>
</html>

Bootstrap

// main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

Bootstrap

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
  imports: [ BrowserModule, UsersModule, appRouting ],
  declarations: [ AppComponent, Home, ... ],
  providers: [ LoginService, AuthCanActivate ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'my-app', // <my-app>Loading...</my-app>
  template: `...`
})
export class App { 
  constructor() { }
}

Services

/home

/users

login.service.ts

import { Injectable } from '@angular/core';
 
@Injectable()
export class LoginService {
  authorised = false;
  
  authorise(value) {
    this.authorised = value;
  }
}

home.component.ts

// home.component.ts
import { Component } from '@angular/core';
import { LoginService } from './login.service';

@Component({
  template: `
    <h1>Home</h1>
    <input #switch type="checkbox" 
      [checked]="loginService.authorised" 
      (click)="change(switch.checked)">
    <div *ngIf="loginService.authorised">
      <a>Today's best superheroe</a>
    </div>`
})
export class Home { 
  constructor(private loginService:LoginService) { }

  change(checked){
    this.loginService.authorise(checked);
  }
}

/users

users.json

[{
  "id": 34,
  "username": "spiderman",
  "roles": ["admin", "user"]
}, {
  "id": 67,
  "username": "batman",
  "roles": ["user"]
}]

users.service.ts

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/retryWhen';
import 'rxjs/add/operator/delay';

@Injectable()
export class UsersService {
  constructor(private http:Http) { }
  
  get(){
    return this.http.get('api/users.json')
      .map(response => response.json())
      .retryWhen(errors => errors.delay(2000));
  }
}

users.component.ts

import { Component } from '@angular/core';
import { UsersService } from './users.service';

@Component({
  selector: 'users',
  template: `
    <h1>Users</h1>
    <table class="table">
      <tr *ngFor="let user of users">
        <td>{{user.username}}</td>
      </tr>
    </table>`
})
export class Users { 
  constructor(private service:UsersService){
    service.get().subscribe(users => this.users = users);
  }
}

Router

Main Contributor

Main Features

  • Url-based navigation
  • Flexible routing
    • Nested and auxiliary routes
  • Navigation Guards
  • Lazy loading

Setup Router

Dependencies

  • Choose Location Strategy
  • Setup routes
  • Include providers and directives
    • RouterModule.forRoot(routes)
    • RouterModule.forChild(routes)

Location Strategies

  • Hash Location
    • Eg: #/home, #/users/34
  • Path Location (default)
    • Requires <base href=.../>
    • Eg: /home, /users/34

Bootstrap

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRouting } from './app.routing';

@NgModule({
  imports: [ BrowserModule, AppRouting, ... ],
  declarations: [ ... ],
  providers: [ ... ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

/home

/users

/about

/users/34

Defining Routes

// app.routing.ts
import { Routes, RouterModule } from '@angular/router';
import { Home } from './home.component';
import { About } from './about.component';
import { NotFound } from './notfound.component';

const appRoutes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', component: Home },
  { path: '**', component: NotFound }, //always last
];

export const AppRouting = RouterModule.forRoot(appRoutes, { useHash: true });

router-outlet

// app.component.ts
@Component({
  selector: 'my-app',
  template: `
    <nav></nav>
    <main>
      <router-outlet></router-outlet>
    </main>`
})
export class App { }

Child Routes

users

#/users

Users

users/:id

#/users/34

User

Child Routes

// users.routing.ts
import { Routes, RouterModule } from '@angular/router';
import { Users } from './users.component';
import { User } from './user.component';
 
const usersRoutes: Routes = [
  { path: 'users', component: Users },
  { path: 'users/:id', component: User }
];

export const UsersRouting = RouterModule.forChild(usersRoutes);

Navigation

  • Using regular links with hash
    • #/home
  • Using routerLink directive
    • ['home']
    • ['users', 34] /users/:id
  • Programatically
    • router.navigate(['users', 34])
    • router.navigateByUrl('/users/34')

routerLink

import { Component } from '@angular/core';
 
@Component({
  selector: 'users',
  template: `
    <h1>Users</h1>
    <tr *ngFor="let user of users">
      <td>
        <a [routerLink]="['/users', user.id]">{{user.username}}</a>
      </td>
    </tr>
  `
})
export class Users { }

Some examples

<!-- Top Routes -->
<a href="#/home">Home</a>
<a [routerLink]="['home']">Home</a>

<!-- Nested Routes -->
<a href="#/users">Users</a>
<a [routerLink]="['users']">Users</a>

<a href="#/users/34">Spiderman</a>
<a [routerLink]="['users', 34]">Spiderman</a>

Some examples

<!-- hash fragments -->
<a href="#/users/34#section">Spiderman</a>
<a [routerLink]="['users', 34]" fragment="section">Spiderman</a>

<!-- params and queryParams -->
<a href="#/users/34;flag=true?q=1">Spiderman</a>
<a [routerLink]="['users', 34, {flag: true}]" [queryParams]="{q: 1}">Spiderman</a>

Current Url

// { path: 'users/:id', component: User } Url: #/users/34
import { Router } from '@angular/router';

@Component({
  selector: 'user-details'
})
export class User implements OnInit { 
  constructor(private router: Router){ }

  ngOnInit() {
    console.log(this.router.url); // /users/34
  }
}

Accessing hash fragment

// { path: 'users/:id', component: User } Url: #/users/34#section
import { Router } from '@angular/router';

@Component({
  selector: 'user-details'
})
export class User implements OnInit { 
  constructor(private router: Router){
    router.routerState.root.fragment.subscribe(f => {
      let fragment = f; // section
    });
  }
  ngOnInit() {
    let fragment = this.router.routerState.snapshot.root.fragment; // section
  }
}

Accessing queryParams

// { path: 'users/:id', component: User } Url: #/users/34?q=1
import { Router } from '@angular/router';

@Component({
  selector: 'user-details'
})
export class User implements OnInit { 
  constructor(private router: Router){
    router.routerState.root.queryParams.subscribe(params => {
      let q = params.q; // 1
    });
  }
  ngOnInit() {
    let q = this.router.routerState.snapshot.root.queryParams.q; // 1
  }
}

Accessing Data

// { path: 'users/:id', component: User, data: { key: 1 } }
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'user-details'
})
export class User implements OnInit { 
  constructor(private route: ActivatedRoute){
    route.data.subscribe(data => {
      let key = data.key; // 1
    });
  }
  ngOnInit() {
    let key = this.route.snapshot.data.key; // 1
  }
}

Accessing Parameters

// { path: 'users/:id', component: User } Url: #/users/34;flag=true
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'user-details'
})
export class User implements OnInit { 
  constructor(private route: ActivatedRoute){
    route.params.subscribe(params => {
      let id = params.id; // 34
    });
  }
  ngOnInit() {
    let id = this.route.snapshot.params.id;  // 34
    let flag = this.route.snapshot.params.flag; // true
  }
}

Navigation Guards

Ask before navigating 

// users.routing.ts
import { Routes, RouterModule } from '@angular/router';
import { UserCanDeactivate } from './user.canDeactivate';

const usersRoutes: Routes = [
  { path: 'users', component: Users },
  { path: 'users/:id', component: User, 
    canDeactivate: [UserCanDeactivate] 
  }
];

export const UsersRouting = RouterModule.forChild(usersRoutes);

Ask before navigating

// user.canDeactivate.ts
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
 
Injectable()
export class UserCanDeactivate implements CanDeactivate {
  canDeactivate() {
    return window.confirm('Do you want to continue?');
  }
}

Restrict Access

// users.routing.ts
import { Routes, RouterModule } from '@angular/router';
import { AuthCanActivate } from './auth.canActivate';

const usersRoutes: Routes = [
  { path: 'users', component: Users },
  { path: 'users/:id', component: User, 
    canDeactivate: [UserCanDeactivate],
    canActivate: [AuthCanActivate]  
  }
];

export const usersRouting = RouterModule.forChild(usersRoutes);

Restrict Access

// auth.canActivate.ts
import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
import { LoginService } from './login.service';

export class AuthCanActivate implements CanActivate {
  constructor(private loginService: LoginService, private router: Router) {}  
  
  canActivate() {
    if (!this.loginService.authorised) {
      this.router.navigate(['/home']);
      return false;
    }
    return true;
  }
}

Lazy Loading

Lazy Loading

// app.routing.ts
const aboutRoutes: Routes = [
  { path: 'about', loadChildren: './app/about.module.ts' },
];
export const AboutRouting = RouterModule.forChild(aboutRoutes);

//about.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AboutRouting } from './about.routing';
import { About } from './about.component';

@NgModule({
  imports: [ CommonModule, AboutRouting ],
  declarations: [ About ]
})
export default class AboutModule { }

Advanced Features

Auxiliary routes

// app.routing.ts
const routes: Routes = [
  {path: 'home', component: HelloCmp},
  {path: 'banner', component: Banner, outlet: 'bottom'}
];
// app.component.ts
@Component({
  selector: 'my-app',
  template: `
    <router-outlet></router-outlet>
    <router-outlet name="bottom"></router-outlet>`,
})
export class App { }
// templates/code
<a href="#/home(bottom:banner)">Show Banner!</a>
<a [routerLink]="['/home(bottom:banner)']">Show Banner!</a>
router.navigate('/home(bottom:banner)');

Resolve

// about.routing.ts
import { Routes, RouterModule } from '@angular/router';
import { About } from './about.component';
import { AboutResolver } from './about.resolver';

const aboutRoutes: Routes = [
  { path: '', component: About, 
    resolve: { magicNumber: AboutResolver } 
  }
];

export const AboutRouting = RouterModule.forChild(aboutRoutes);

Resolve

// about.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';

@Injectable()
export class AboutResolver implements Resolve {
  resolve() {
    console.log('Waiting to resolve...');
    return new Promise(resolve => setTimeout(() => resolve(4545), 2000));
  }
}

Resolve

// about.component.ts
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'about',
  template: `
    <h1>About</h1>
    <p>Magic Number: {{magicNumber}}</p>`
})
export class About {
  magicNumber: number;
  
  constructor(private route: ActivatedRoute){
    this.magicNumber = route.snapshot.data.magicNumber;
  }
}

Demo

Features

  • Nested routes with parameters
  • Asking before leaving a route
  • Restricting route access
  • Cool SVG loading spinner
  • Lazy Loading and resolve

Thanks!

The Amazing Angular 2 Router

By Gerard Sans

The Amazing Angular 2 Router

Angular 2 comes with a new router. We will review its main features and show how we can use navigation guards, secure sections, use nested routes and do lazy load while quickly building a prototype for a very special client!

  • 4,717