THE AMAZING

 Amazing SpiderMan slant

 

by Gerard Sans (@gerardsans)

ROUTER v3

Google Developer Expert

Master of Ceremonies

International Speaker

Angular 2 Trainer

Community Leader

800

500

1x 35% discount ng-book 2 (£13)

@angular_zone #jslnd

Some days before the meetup...

Angular 2

Features

  • Latest Web Standards
  • Simple
  • Lightning fast
  • Works everywhere

ES5 / ES6 / TypeScript

ES6 (ES2015)

- Classes, modules, arrow functions

TypeScript

- Types, annotations

- Better editor support

TypeScript IDE Support

Application Design

Component Tree

source: blog

/home

/users

/about

/users/34

Bootstrap

Bootstrapping

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

index.html

<!DOCTYPE html>
<html>
  <head>
    <!-- Polyfill(s) for older browsers -->
    <script src=".../shim.min.js"></script>
    <script src=".../zone.js@0.6.12?main=browser"></script>
    <script src=".../reflect-metadata@0.1.3"></script>
    <script src=".../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 { bootstrap } from '@angular/platform-browser-dynamic';
import { App } from './app.component';
import { LoginService } from './login.service';
...

bootstrap(App, [
  // Global dependencies
  LoginService, // Singleton
])
.catch(err => console.error(err));

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 | async">
        <td>{{user.username}}</td>
      </tr>
    </table>`
})
export class Users { 
  constructor(private service:UsersService){
    this.users = service.get();
  }
}

Component Router

Main Contributor

Main Features

  • Based on components
  • Flexible routing
    • Nested views, auxiliary routes
  • Navigation Gards
  • Lazy loading (rc.5)

Setup Router

Dependencies

  • Choose Location Strategy
  • Setup routes
  • Include providers
    • provideRouter(routes)
  • Include directives
    • ROUTER_DIRECTIVES

Location Strategies

  • HashLocationStrategy
    • Eg: #/home, #/users/34
  • PathLocationStrategy (default)
    • Requires <base href=.../>
    • Eg: /home, /users/34

Bootstrap

import { provideRouter } from '@angular/router';
import { HashLocationStrategy, LocationStrategy } from '@angular/common';
import { routes } from './app.routes';

bootstrap(App, [
  provideRouter(routes),
  { provide: LocationStrategy, useClass: HashLocationStrategy },
])


/home

/users

/about

/users/34

Defining Routes

// app.routes.ts
import { Home } from './home.component';
import { About } from './about.component';
import { NotFound } from './notfound.component';

export const routes: RouterConfig = [
  { path: '',  redirectTo: 'home', pathMatch: 'full' },
  { path: 'home',  component: Home },
  { path: 'about',  component: About },
  { path: '**', component: NotFound }, //always last
];

router-outlet

// app.component.ts
import { ROUTER_DIRECTIVES }  from '@angular/router';

@Component({
  selector: 'my-app',
  template: `
    <nav></nav>
    <main>
      <router-outlet></router-outlet>
    </main>`,
  directives: [ROUTER_DIRECTIVES]
})
export class App { }

Child Routes

users

#/users

Users

users/:id

#/users/34

User

Child Routes

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

Navigation

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

routerLink

import { Component } from '@angular/core';
import { ROUTER_DIRECTIVES } from '@angular/router';
 
@Component({
  selector: 'users',
  template: `
    <h1>Users</h1>
    <tr *ngFor="let user of users">
      <td>
        <a [routerLink]="['/users', user.id]">{{user.username}}</a>
      </td>
    </tr>
  `,
  directives: [ROUTER_DIRECTIVES]
})
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>

<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 }
import { ActivatedRoute, Router } from '@angular/router';

@Component({
  selector: 'user-details'
})
export class User implements OnInit { 
  constructor(private route: ActivatedRoute, private router: Router){ 
    this.route.url.subscribe(s => 
      console.log(router.createUrlTree(s).toString())  // /users/34
    );
  }
  ngOnInit() {
    console.log(this.router.url); // /users/34
  }
}

Accessing Parameters

// { path: 'users/:id', component: User }
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;
    });
  }
  ngOnInit() {
    let id = this.route.snapshot.params.id;
  }
}

Accessing queryParams

// #/users/34;flag=true?q=1
import { Router } from '@angular/router';

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

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(v => console.log(v));
  }
  ngOnInit() {
    let data = this.route.snapshot.data.key;
  }
}

Navigation Gards

Ask before navigating 

// users.routes.ts
import { RouterConfig } from '@angular/router';
import { UserCanDeactivate } from './user.canDeactivate';

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

Ask before navigating

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

Restrict Access

// users.routes.ts
import { RouterConfig } from '@angular/router';
import { AuthCanActivate } from './auth.canActivate';

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

Restrict Access

// auth.canActivate.ts
import { CanActivate, Router } 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;
  }
}

Resolve

// app.routes.ts
import { AboutResolver } from './about.resolver';

export const routes: RouterConfig = [
  { path: 'home',  component: Home },
  { 
    path: 'about',  component: About, 
    resolve: { magicNumber: AboutResolver } 
  },
];

Resolve

// about.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class AboutResolver implements Resolve {
  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):Observable<any> {
    console.log('Waiting to resolve...');
    let p = new Promise(resolve => setTimeout(() => resolve(4545), 2000));
    return Observable.from(p);
  }
}

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

Advanced Features

Auxiliary routes

// app.routes.ts
export const routes: RouterConfig = [
  {path: 'home', component: HelloCmp},
  {path: 'banner', component: BottomCmp, 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)');

Lazy Loading (v2)

@RouteConfig([
  { path: '/about', loader: AboutLazyLoader , name: 'AboutLazyLoad' }
])
export class App { }

//aboutLazyLoader.ts
export function AboutLazyLoader(){
  return System.import('./src/aboutLazyLoad.ts')
    .then(module => module.AboutLazyLoad);
}

//aboutLazyLoad.ts
@Component({
  selector: 'about',
  template: `<h1>About</h1>`
})
export class AboutLazyLoad { }

Demo

Features

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

AngularConnect - Sept 27-28th

@AngularConnect

Thanks!