1 of 50

N Things You Don’t Know �About the Router

Deborah Kurata

Developer | Author | Consultant | MVP | GDE

@deborahkurata

N > 0

2 of 50

How Routing Works

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

�@Component({�  templateUrl: 'movie-list.component.html'�})�export class MovieListComponent { }

movie-list.component.ts

<a [routerLink]="['/movies']">Movie List</a>

{ path: 'movies', component: MovieListComponent }

<router-outlet></router-outlet>

3 of 50

To Menu�or Not To Menu

4 of 50

5 of 50

<div class="container">

<nav class="navbar navbar-default">

<div class="container-fluid">

<a class="navbar-brand">{{pageTitle}}</a>

<ul class="nav navbar-nav">

<li><a [routerLink]="['/welcome']">Home</a></li>

<li><a [routerLink]="['/movies']">Movie List</a></li>

</ul>

</div>

</nav>

<div class="container">

<router-outlet></router-outlet>

</div>

</div>

app.component.html

6 of 50

app �router outlet

7 of 50

<div class="container">

<nav class="navbar navbar-default" *ngIf="displayMenu">

<div class="container-fluid">

<a class="navbar-brand">{{pageTitle}}</a>

<ul class="nav navbar-nav">

<li><a [routerLink]="['/welcome']">Home</a></li>

<li><a [routerLink]="['/movies']">Movie List</a></li>

</ul>

</div>

</nav>

<div class="container">

<router-outlet></router-outlet>

</div>

</div>

app.component.html

8 of 50

<router-outlet></router-outlet>

app.component.html

9 of 50

app�router outlet

10 of 50

app�router outlet

11 of 50

shell.component.html

<div class="container">

<nav class="navbar navbar-default">

<div class="container-fluid">

<a class="navbar-brand">{{pageTitle}}</a>

<ul class="nav navbar-nav">

<li><a [routerLink]="['/welcome']">Home</a></li>

<li><a [routerLink]="['/movies']">Movie List</a></li>

</ul>

</div>

</nav>

<div class="container">

<router-outlet></router-outlet>

</div>

</div>

12 of 50

shell.component.html

<mh-menu></mh-menu>

<div class='container'>

<router-outlet></router-outlet>

</div>

13 of 50

shell�router outlet

app�router outlet

14 of 50

app-routing.module.ts

RouterModule.forRoot([

{

path: '', component: ShellComponent,

children: [

{ path: 'welcome', component: WelcomeComponent },

{ path: 'movies', component: MovieListComponent },

{ path: '', redirectTo: 'welcome', pathMatch: 'full' }

]

},

{ path: 'login', component: LoginComponent },

{ path: '**', component: PageNotFoundComponent }

])

15 of 50

16 of 50

Preventing�Partial Page Display

17 of 50

18 of 50

Build a Resolver Service

@Injectable()

export class MovieResolver implements Resolve<IMovie> {

constructor(private movieService: MovieService) { }

� resolve(route: ActivatedRouteSnapshot,

state: RouterStateSnapshot): Observable<IMovie> {

const id = route.paramMap.get('id');

return this.movieService.getMovie(+id);

}

}

movie.resolver.ts

19 of 50

Register the Resolver

app.module.ts

providers: [

MovieService,

MovieResolver

]

20 of 50

Add Resolver to Route

{

path: 'movies/:id',

resolve: { movie: MovieResolver },

component: MovieDetailComponent

},

app-routing.module.ts

21 of 50

Read Resolver Data

ngOnInit(): void {

this.movie = this.route.snapshot.data['movie'];

}

{

path: 'movies/:id',

resolve: { movie: MovieResolver },

component: MovieDetailComponent

},

app-routing.module.ts

movie-detail.component.ts

22 of 50

23 of 50

I'm Waiting …

24 of 50

25 of 50

Subscribe to Router Events

constructor(private router: Router) {

router.events.subscribe((routerEvent: Event) => {

this.checkRouterEvent(routerEvent);

});

}

app.component.ts

26 of 50

Check Router Events

checkRouterEvent(routerEvent: Event): void {

if (routerEvent instanceof NavigationStart) {

this.loading = true;

}

if (routerEvent instanceof NavigationEnd ||

routerEvent instanceof NavigationCancel ||

routerEvent instanceof NavigationError) {

this.loading = false;

}

}

app.component.ts

27 of 50

Display the Spinner

<span class="glyphicon glyphicon-refresh glyphicon-spin spinner"

*ngIf="loading"></span>

<router-outlet></router-outlet>

app.component.html

28 of 50

29 of 50

What’s going on?

30 of 50

31 of 50

app-routing.module.ts

RouterModule.forRoot([

{

path: '', component: ShellComponent,

children: [

{ path: 'welcome', component: WelcomeComponent },

{ path: 'movies', component: MovieListComponent },

{ path: '', redirectTo: 'welcome', pathMatch: 'full' }

]

},

{ path: 'login', component: LoginComponent },

{ path: '**', component: PageNotFoundComponent }

], { enableTracing: true })

32 of 50

Guards at the Ready

33 of 50

34 of 50

Build a Guard Service

@Injectable()

export class MovieEditGuard implements� CanDeactivate<MovieEditComponent> {

� canDeactivate(component: MovieEditComponent): boolean {

if (component.isDirty) {

const title = component.movie.title || 'New Movie';

return confirm(`Lose all changes to ${title}?`);

}

return true;

}

}

movie-edit.guard.ts

35 of 50

Register the Guard

app.module.ts

providers: [

MovieService,

MovieResolver,

MovieEditGuard

]

36 of 50

Add Guard to Route

{

path: 'movies/:id/edit',

resolve: { movie: MovieResolver },

canDeactivate: [ MovieEditGuard ],

component: MovieEditComponent

}

app-routing.module.ts

37 of 50

38 of 50

Keep Constant

39 of 50

app-routing.module.ts

@NgModule({

imports: [

RouterModule.forRoot([

{

path: '', component: ShellComponent,

children: [

{ path: 'welcome', component: WelcomeComponent },

{ path: 'movies', component: MovieListComponent },

{ path: 'movies/:id', component: MovieDetailComponent },

{

path: 'movies/:id/edit', component: MovieEditComponent,

children: [

{ path: '', redirectTo: 'info', pathMatch: 'full' },

{ path: 'info', component: MovieEditInfoComponent },

{ path: 'tags', component: MovieEditTagsComponent }

]

},

{ path: '', redirectTo: 'welcome', pathMatch: 'full' }

]

},

{ path: 'login', component: LoginComponent },

{ path: '**', component: PageNotFoundComponent }

])

],

exports: [RouterModule]

})

export class AppRoutingModule { }

40 of 50

app-routing.module.ts

const appRoutes: Routes = [

{

path: '', component: ShellComponent,

children: [

{ path: 'welcome', component: WelcomeComponent },

{ path: 'movies', component: MovieListComponent },

{ path: 'movies/:id', component: MovieDetailComponent },

{ path: 'movies/:id/edit', component: MovieEditComponent,

children: [

{ path: '', redirectTo: 'info', pathMatch: 'full' },

{ path: 'info', component: MovieEditInfoComponent },

{ path: 'tags', component: MovieEditTagsComponent }

]

},

{ path: '', redirectTo: 'welcome', pathMatch: 'full' }

]

},

{ path: 'login', component: LoginComponent },

{ path: '**', component: PageNotFoundComponent }

];

41 of 50

app-routing.module.ts

@NgModule({

imports: [

RouterModule.forRoot(appRoutes)

],

exports: [RouterModule]

})

export class AppRoutingModule { }

42 of 50

It’s a Feature

43 of 50

What About Feature Modules?

app-routing.module.ts

const appRoutes: Routes = [

{

path: '', component: ShellComponent,

children: [

{ path: 'welcome', component: WelcomeComponent },

{ path: 'movies', component: MovieListComponent },

{ path: 'movies/:id', component: MovieDetailComponent },

{ path: 'movies/:id/edit', component: MovieEditComponent,

children: [

{ path: '', redirectTo: 'info', pathMatch: 'full' },

{ path: 'info', component: MovieEditInfoComponent },

{ path: 'tags', component: MovieEditTagsComponent }

]

},

{ path: '', redirectTo: 'welcome', pathMatch: 'full' }

]

},

{ path: 'login', component: LoginComponent },

{ path: '**', component: PageNotFoundComponent }

];

44 of 50

movie.module.ts

const movieRoutes: Routes = [

{ path: 'movies', component: MovieListComponent } ,

{ path: 'movies/:id', component: MovieDetailComponent },

{ path: 'movies/:id/edit', component: MovieEditComponent,

children: [

{ path: '', redirectTo: 'info', pathMatch: 'full' },

{ path: 'info', component: MovieEditInfoComponent },

{ path: 'tags', component: MovieEditTagsComponent }

]

}

];

@NgModule({

imports: [ ...,

RouterModule.forChild(movieRoutes) // Won’t work!

], ...

})

export class MovieModule { }

45 of 50

Merged Routes

imports: [

MovieModule,

RouterModule.forRoot(appRoutes)

]

{ path: 'movies', component: MovieListComponent } ,

{ path: 'movies/:id', component: MovieDetailComponent },

{ path: 'movies/:id/edit', component: MovieEditComponent,

children: [

{ path: '', redirectTo: 'info', pathMatch: 'full' },

{ path: 'info', component: MovieEditInfoComponent },

{ path: 'tags', component: MovieEditTagsComponent }

]

}

{ path: '', component: ShellComponent,

children: [

{ path: 'welcome', component: WelcomeComponent },

{ path: '', redirectTo: 'welcome', pathMatch: 'full' }

]

},

{ path: 'login', component: LoginComponent },

{ path: '**', component: PageNotFoundComponent }

46 of 50

Lazy Loading

47 of 50

From: Angular Routing course on Pluralsight

48 of 50

loadChildren

app-routing.module.ts

{

path: '', component: ShellComponent,

children: [

{ path: 'welcome', component: WelcomeComponent },

{

path: 'movies',

loadChildren: './movies/movie.module#MovieModule'

},

{ path: '', redirectTo: 'welcome', pathMatch: 'full' }

]

},

{ path: 'login', component: LoginComponent },

{ path: '**', component: PageNotFoundComponent }

49 of 50

movie.module.ts

const movieRoutes: Routes = [

{ path: '', component: MovieListComponent } ,

{ path: ':id', component: MovieDetailComponent },

{ path: ':id/edit', component: MovieEditComponent,

children: [

{ path: '', redirectTo: 'info', pathMatch: 'full' },

{ path: 'info', component: MovieEditInfoComponent },

{ path: 'tags', component: MovieEditTagsComponent }

]

}

];

@NgModule({

imports: [ ...,

RouterModule.forChild(movieRoutes)

], ...

})

export class MovieModule { }

50 of 50

For More Information

Twitter: @deborahkurata

Code: https://github.com/DeborahK/MovieHunter-routing

Slides: bit.ly/ngConf18

Course: bit.ly/Angular-routing