�N Things You Don’t Know �About the Router
Deborah Kurata
Developer | Author | Consultant | MVP | GDE
@deborahkurata
N > 0
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>
To Menu�or Not To Menu
<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
app �router outlet
<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
<router-outlet></router-outlet>
app.component.html
app�router outlet
app�router outlet
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>
shell.component.html
<mh-menu></mh-menu>
�<div class='container'>
<router-outlet></router-outlet>
</div>
shell�router outlet
app�router outlet
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 }
])
Preventing�Partial Page Display
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
Register the Resolver
app.module.ts
providers: [
MovieService,
MovieResolver
]
Add Resolver to Route
{
path: 'movies/:id',
resolve: { movie: MovieResolver },
component: MovieDetailComponent
},
app-routing.module.ts
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
I'm Waiting …
Subscribe to Router Events
constructor(private router: Router) {
router.events.subscribe((routerEvent: Event) => {
this.checkRouterEvent(routerEvent);
});
}
app.component.ts
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
Display the Spinner
<span class="glyphicon glyphicon-refresh glyphicon-spin spinner"
*ngIf="loading"></span>
<router-outlet></router-outlet>
app.component.html
What’s going on?
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 })
Guards at the Ready
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
Register the Guard
app.module.ts
providers: [
MovieService,
MovieResolver,
MovieEditGuard
]
Add Guard to Route
{
path: 'movies/:id/edit',
resolve: { movie: MovieResolver },
canDeactivate: [ MovieEditGuard ],
component: MovieEditComponent
}
app-routing.module.ts
Keep Constant
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 { }
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 }
];
app-routing.module.ts
@NgModule({
imports: [
RouterModule.forRoot(appRoutes)
],
exports: [RouterModule]
})
export class AppRoutingModule { }
It’s a Feature
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 }
];
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 { }
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 }
Lazy Loading
From: Angular Routing course on Pluralsight
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 }
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 { }
For More Information
Twitter: @deborahkurata
Code: https://github.com/DeborahK/MovieHunter-routing
Slides: bit.ly/ngConf18
Course: bit.ly/Angular-routing