THE AMAZING

   ANGULAR 2

ROUTER

slides.com/gerardsans | @gerardsans

Google Developer Expert

International Speaker

Master of Ceremonies

Angular 2 Trainer

Blogger

Community Leader

800

600

Some days before

Jfokus...

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>
    <script src="https://unpkg.com/zone.js/dist/zone.js"></script>
    <script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
    <script src="https://unpkg.com/systemjs@0.19.31/dist/system.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
    • Child and auxiliary routes
  • Navigation Guards
  • Lazy loading and preloading
  • Resolve

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);

Child Routes - refactored

// users.routing.ts
import { Routes, RouterModule } from '@angular/router';
import { Users } from './users.component';
import { User } from './user.component';
 
const usersRoutes: Routes = [
  { path: 'users',
    children: [
      { path: '', component: Users },
      { path: ':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>

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

Passing data

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

<!-- 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 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
  }
}

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
  }
}

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 { }

Preloading

default preloading

// app.routing.ts
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';

const appRoutes: Routes = [
  { path: 'about', loadChildren: './app/about.module.ts' }, 
];

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

Wrap-up

Requirements

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

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 gards, secure sections, use nested routes and do lazy load while quickly building a fully working prototype! The Angular 2 Router is one of the most fundamental pieces in any Angular 2 Application. We will cover it's main features. Possibly the most interesting one is the ability to Lazy Load Components on-demand. We will go through all the steps to cover the common scenarios and some more advanced ones. Auxiliary routes allow totally independent navigations so you can handle complex scenarios with ease. We will show common pitfalls and best practices to take advantage of them. Content will be updated accordingly to use latest Angular 2 release available.

  • 4,231