Angular Router
Gonzalo Ruiz de Villa @gruizdevilla
CTO @gft_es
Angular Madrid Meetup
https://github.com/domenic/zones
¿Qué es un Router?
The Angular Router enables navigation from one view to the next as users perform application tasks.
Principales características
- Navegación por URLs
- Routing flexible:
- rutas hijas
- rutas auxiliares
- Guardias en la navegación
- Lazy loading & preloading
- Resolve
https://leanpub.com/router
Angular 2 Router
by Victor Savkin
Bootstrap
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>AmRouter</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>
index.html
Bootstrap
import './polyfills.ts';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
main.ts
Plantillas
<md-toolbar color="accent">¡Bienvenido!</md-toolbar>
<md-card>
<md-card-content>
<h2 class="example-h2">Estado actual</h2>
<md-slide-toggle
[checked]="authService.logged"
(change)="authService.logged=!authService.logged">
Logado
</md-slide-toggle>
<!-- ... -->
</md-card-content>
</md-card>
bienvenido/bienvenido.component.html
Componentes
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth.service';
@Component({
selector: 'am-bienvenido',
templateUrl: './bienvenido.component.html',
styleUrls: ['./bienvenido.component.css']
})
export class BienvenidoComponent implements OnInit {
constructor(private authService: AuthService) { }
ngOnInit() {
}
}
bienvenido/bienvenido.component.ts
Servicios
import { Injectable } from '@angular/core';
import { Router, CanActivate} from '@angular/router';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of';
@Injectable()
export class AuthService implements CanActivate {
logged:boolean;
constructor() {
this.logged = false;
}
canActivate(){
return Observable.of(this.logged);
}
}
auth.service.ts
Servicios
import { Injectable } from '@angular/core';
import { Song } from './entities/song';
import { Album } from './entities/album';
@Injectable()
export class MusicService {
//...
selectedSongs(): Song[] { return playList; }
albums(): Album[]{ return albumList; }
songs(): Song[] { return songList; }
music.service.ts
Configuración del módulo
@NgModule({
declarations: [
AppComponent,
PlayerComponent,
SonglistComponent,
AlbumgridComponent,
BienvenidoComponent,
//...
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
AppRoutingModule,
MaterialModule.forRoot()
],
entryComponents: [
DeactivatedialogComponent
],
providers: [AuthService, DeactivateService, MusicService],
bootstrap: [AppComponent]
})
export class AppModule { }
app.module.ts
Setup
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
//...
@NgModule({
declarations: [
//...
],
imports: [
//...
AppRoutingModule,
//...
],
entryComponents: [//... ],
providers: [//...],
bootstrap: [AppComponent]
})
export class AppModule { }
Incluir módulo router
RouterModule.forRoot(routes)
RouterModule.forChild(routes)
Definiendo las rutas
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { BienvenidoComponent } from './bienvenido/bienvenido.component';
const routes: Routes = [
{ path: '', redirectTo: '/bienvenido', pathMatch: 'full' },
{ path: 'bienvenido', component: BienvenidoComponent }
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
app-routing.module.ts
Pintamos en outlets
<md-sidenav-layout>
<md-sidenav #sidenav mode="side" class="app-sidenav"></md-sidenav>
<md-toolbar color="primary">
Angular Router Example App
</md-toolbar>
<div class="app-content">
<router-outlet></router-outlet>
</div>
</md-sidenav-layout>
app.component.html
Rutas jerárquicas
const routes: Routes = [
{ path: '', redirectTo: '/bienvenido', pathMatch: 'full' },
{ path: 'bienvenido', component: BienvenidoComponent },
{ path: 'albumes', component: AlbumgridComponent},
{ path: 'albumes/:albumTitle', component: AlbumdetailComponent}
];
app-routing.module.ts
Rutas jerárquicas (refactor)
const routes: Routes = [
{ path: '', redirectTo: '/bienvenido', pathMatch: 'full' },
{ path: 'bienvenido', component: BienvenidoComponent },
{ path: 'albumes',
children: [
{path:'', component: AlbumgridComponent},
{path:':albumTitle', component: AlbumdetailComponent}
]
}
];
app-routing.module.ts
Navegando
Con directiva routerLink:
-
routerLink="/licencia"
-
[routerLink]="['/albumes', album.title]"
-
[routerLink]="['/',{ outlets: { 'side': ['player'] } }]"
Navegando
Programáticamente:
-
this.router.navigate(['bienvenido']);
-
this.router.navigateByUrl('/bienvenido');
routerLink
<md-card-content>
<h2 class="example-h2">Estado actual</h2>
<md-slide-toggle [checked]="authService.logged" (change)="authService.logged=!authService.logged">
Logado
</md-slide-toggle>
<p>Debes estar logado para acceder al listado de álbumes</p>
<p><button md-raised-button color="primary" routerLink="/albumes">Ver Álbumes</button></p>
<p>Puedes consultar la <a routerLink="/licencia">licencia</a></p>
<p>Puedes ver algo <a routerLink="/lazy">lazy</a></p>
</md-card-content>
bienvenido.component.html
routerLink
<p>
<button md-raised-button
[routerLinkActive]="['active']"
[routerLink]="['/',{ outlets: { 'side': ['player'] } }]"
[queryParams]="{ play: 1 }">Play</button>
<button md-raised-button
[routerLinkActive]="['active']"
[routerLink]="['/',{ outlets: { 'side': ['player'] } }]"
[queryParams]="{ play: 0 }">Stop</button>
</p>
albumgrid.component.html
routerLink
<!-- params and queryParams -->
<a href="#/albumes/Blue-again;flag=true?q=1">Blue again</a>
<a [routerLink]="['albumes', 'Blue again', {flag: true}]"
[queryParams]="{q: 1}">Blue again</a>
más opciones
Para acceder a la url usamos Router
@Component({
selector: 'am-albumdetail'
})
export class AlbumdetailComponent implements OnInit {
constructor(private router: Router){ }
ngOnInit() {
console.log(this.router.url); // /albumes/Blue-again
}
}
Accediendo a los datos de la url
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'am-albumdetail',
})
export class AlbumdetailComponent implements OnInit {
constructor(
private musicService:MusicService,
private route: ActivatedRoute
) {}
ngOnInit() {
let title = this.route.snapshot.params['albumTitle'];
//...
}
}
albumdetail.component.ts
Observando los datos de la url
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'am-navmusic'
})
export class NavmusicComponent implements OnInit, OnDestroy {
play: string;
constructor( private route: ActivatedRoute) { }
ngOnInit() {
this.sub = this.route
.queryParams
.subscribe(params => {
this.play = params["play"];
//...
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
navmusic.component.ts
Guardias: preguntar al salir
import { LicenciaComponent } from './licencia/licencia.component';
import { DeactivateService } from './deactivate.service';
const routes: Routes = [
{ path: '', redirectTo: '/bienvenido', pathMatch: 'full' },
{ path: 'bienvenido', component: BienvenidoComponent },
{
path: 'licencia',
component: LicenciaComponent,
canDeactivate:[DeactivateService]
},
];
app-routing.module.ts
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { MdDialog} from '@angular/material';
import { DeactivatedialogComponent } from './deactivatedialog/deactivatedialog.component';
import { LicenciaComponent } from './licencia/licencia.component';
@Injectable()
export class DeactivateService implements CanDeactivate<LicenciaComponent> {
constructor(private dialog: MdDialog) { }
canDeactivate(target: LicenciaComponent) {
return this.dialog.open(DeactivatedialogComponent).afterClosed();
}
}
deactivate.service.ts
import { Component, OnInit } from '@angular/core';
import { MdDialogRef } from '@angular/material';
@Component({
selector: 'app-deactivatedialog',
templateUrl: './deactivatedialog.component.html',
styleUrls: ['./deactivatedialog.component.css']
})
export class DeactivatedialogComponent implements OnInit {
constructor(public dialogRef: MdDialogRef<DeactivatedialogComponent>) { }
ngOnInit() {
}
}
deactivatedialog.component.ts
<h1 md-dialog-title>¿Estas seguro?</h1>
<div md-dialog-content>
<p>¿Seguro que quieres salir? Si es la mar de interesante.</p>
</div>
<div md-dialog-actions>
<button md-raised-button
color="primary"
(click)="dialogRef.close(true)">Por supuesto</button>
<button md-raised-button
color="warn"
(click)="dialogRef.close(false)">Bueno... me quedo</button>
</div>
deactivatedialog.component.html
Restringiendo accesos: validando al entrar
const routes: Routes = [
{ path: '', redirectTo: '/bienvenido', pathMatch: 'full' },
{ path: 'bienvenido', component: BienvenidoComponent },
{ path: 'licencia', component: LicenciaComponent, canDeactivate:[DeactivateService] },
{ path: 'albumes', canActivate:[AuthService] ,
children: [
{path:'', component: AlbumgridComponent},
{path:':albumTitle', component: AlbumdetailComponent}
]
}
];
app-routing.module.ts
import { Injectable } from '@angular/core';
import { Router, CanActivate} from '@angular/router';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of';
@Injectable()
export class AuthService implements CanActivate {
logged:boolean;
constructor() {
this.logged = false;
}
canActivate(){
return Observable.of(this.logged);
}
}
auth.service.ts
Rutas auxiliares
const routes: Routes = [
{ path: '', redirectTo: '/bienvenido', pathMatch: 'full' },
{ path: 'bienvenido', component: BienvenidoComponent },
{ path: 'licencia', component: LicenciaComponent, canDeactivate:[DeactivateService] },
{ path: 'albumes', canActivate:[AuthService] ,
children: [
{path:'', component: AlbumgridComponent},
{path:':albumTitle', component: AlbumdetailComponent}
]
},
{ path: 'player', component: NavmusicComponent, outlet: 'side'}
]
app-routing.module.ts
<md-sidenav #sidenav mode="side" class="app-sidenav">
<router-outlet name="side"></router-outlet>
<p>
<button md-raised-button color="primary"
[routerLinkActive]="['hidebutton']"
[routerLink]="[{ outlets: { 'side': ['player'] } }]"
>Load player</button>
</p>
</md-sidenav>
app.component.html
<p>
<button md-raised-button
[routerLinkActive]="['active']"
[routerLink]="['/',{ outlets: { 'side': ['player'] } }]"
[queryParams]="{ play: 1 }">Play</button>
<button md-raised-button
[routerLinkActive]="['active']"
[routerLink]="['/',{ outlets: { 'side': ['player'] } }]"
[queryParams]="{ play: 0 }">Stop</button>
</p>
albumgrid.component.html
albumgrid.component.css
.active{font-style:italic;}
Lazy loading
Lazy loading
const routes: Routes = [
//...
{ path: 'lazy', loadChildren: 'app/lazy/lazy.module#LazyModule' }
];
app-routing.module.ts
Lazy loading
import { NgModule } from '@angular/core';
import { MaterialModule } from '@angular/material';
import { LazyComponent } from './lazy.component';
import { routing } from './lazy.routing';
@NgModule({
imports: [routing, MaterialModule.forRoot()],
declarations: [LazyComponent]
})
export class LazyModule {}
lazy.module.ts
Lazy loading
import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LazyComponent } from './lazy.component';
const routes: Routes = [
{ path: '', component: LazyComponent }
];
export const routing: ModuleWithProviders = RouterModule.forChild(routes);
lazy.routing.ts
Lazy loading
import { Component } from '@angular/core';
@Component({
template: `<md-card>
<md-card-content>
<p>¡Esto es lazy!</p>
</md-card-content>
</md-card>`
})
export class LazyComponent {}
lazy.component.ts
Estrategias de Lazy Loading
@NgModule({
bootstrap: [AppCmp],
imports: [
RouterModule.forRoot(ROUTES,
{preloadingStrategy: PreloadAllModules})
]
})
class AppModule {}
Preloading
@NgModule({
bootstrap: [AppCmp],
imports: [
RouterModule.forRoot(ROUTES,
{preloadingStrategy: NoPreloading})
]
})
class AppModule {}
Sin preloading
Estrategias a medida de Lazy Loading
[
{
path: 'moduleA',
loadChildren: './moduleA.module',
data: {preload: true}
},
{
path: 'moduleB',
loadChildren: './moduleB.module'
}
]
Agregamos un nuevo atributo a los datos de las rutas
Estrategias a medida de Lazy Loading
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: Function): Observable<any> {
return route.data && route.data.preload ? load() : of(null);
}
}
Implementamos la estrategia usando la interfaz PreloadingStrategy
Configuramos el módulo con la nueva estrategia
@NgModule({
bootstrap: [AppCmp],
providers: [CustomPreloadingStrategy],
imports: [
RouterModule.forRoot(
ROUTES,
{preloadingStrategy: CustomPreloadingStrategy}
)
]
})
class AppModule {}
Revisión de características
- Navegación por URLs
- Routing flexible:
- rutas hijas
- rutas auxiliares
- Guardias en la navegación
- Lazy loading & preloading
Pendiente para agregar al ejemplo
- Resolve
- Inner outlets
¡Gracias!
@gruizdevilla
@gft_es
Angular Router
By Gonzalo Ruiz de Villa
Angular Router
- 3,185