Freddy Montes
Frontend Developer with a graphic design degree. UX/UI and Javascript crafter.
Lazy Load
Guards
Resolvers
FREDDY MONTES
AUTOR:
💼 Team Lead Frontend Developer @ DotCMS
🐦 @fmontes
📷 @fmontes
🌎 http://fmontes.com
✉️ me@fmontes.com
An Angular application is a tree of components. (...) In other words, a router state is an arrangement of application components that defines what is visible on the screen.
Victor Savkin in Angular Router: Understanding Router State
ng new angular-cr-routing
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
Es una directiva de la librería que se usa como un componente. Actúa como un placeholder que marca el sitio en el template donde el router va a mostrar los componentes.
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>Welcome to {{ title }}!</h1>
<img
width="300"
alt="Angular Logo"
src="data:image/..."
/>
</div>
<h2>Here are some links to help you start:</h2>
<ul>
<li>
<h2>
<a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a>
</h2>
</li>
<li>
<h2>
<a target="_blank" rel="noopener" href="https://angular.io/cli">CLI Documentation</a>
</h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
</li>
</ul>
<router-outlet></router-outlet>
<header>
<h1>Header</h1>
</header>
<router-outlet></router-outlet>
<footer>
<p>Footer</p>
</footer>
ng serve
ng g component pages/home
ng g component pages/costa-rica
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './pages/home/home.component';
import { CostaRicaComponent } from './pages/costa-rica/costa-rica.component';
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'cr',
component: CostaRicaComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
Es una directiva que se usa en los anchor links <a> y solo tenemos que pasarle el string del path.
<nav>
<ul>
<li>
<a
routerLink="/"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>Home</a>
</li>
<li>
<a
routerLink="/cr"
routerLinkActive="active"
>Costa Rica</a>
</li>
</ul>
</nav>
ng g m pages/beaches --routing
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class BeachesRoutingModule {}
ng g c pages/beaches
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BeachesComponent } from './beaches.component';
const routes: Routes = [{
path: '',
component: BeachesComponent
}];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class BeachesRoutingModule {}
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'cr',
component: CostaRicaComponent,
},
{
path: 'beaches',
loadChildren: () => import('./pages/beaches/beaches.module').then(m => m.BeachesModule)
}
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
enableTracing: true
})],
exports: [RouterModule]
})
Un guard es una clase con métodos que retornan un boolean o un Observable o Promise de boolean para cargar o no una ruta.
Metodos | Para mediar |
---|---|
CanActivate | en la navegación de una ruta |
CanActivateChild | en la navegación a una ruta hijo |
CanDeactivate | cuando se navega fuera de una ruta |
CanLoad | en la navegación de un módulo que se carga asíncronamente |
ng g guard guards/auth
export class AuthGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return true;
}
}
import { AuthGuard } from './guards/auth.guard';
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'cr',
component: CostaRicaComponent,
canActivate: [AuthGuard]
},
{
path: 'beaches',
loadChildren: './pages/beaches/beaches.module#BeachesModule'
}
];
export class AuthService {
private user$: Subject<string> = new Subject();
login(): void {
const user = Math.random()
.toString(36)
.substr(2);
localStorage.setItem('user', user);
this.user$.next(user);
}
logout(): void {
localStorage.removeItem('user');
this.user$.next(null);
}
isAuth(): Observable<string> {
const user = localStorage.getItem('user');
return merge(of(user), this.user$.asObservable());
}
}
<div *ngIf="(user$ | async) as user; else loginButton">
<p>User: {{ user }}</p>
<button (click)="logout()">Logout</button>
</div>
<ng-template #loginButton>
<button (click)="login()">Login</button>
</ng-template>
import { AuthService } from '../services/auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.authService.isAuth().pipe(map((token: string) => !!token));
}
}
export interface Beach {
id: string;
name: string;
desc?: string;
img?: string;
}
@Injectable({
providedIn: 'root'
})
export class BeachesService {
constructor() {}
getBeaches(): Observable<Beach[]> {
return from(data).pipe(
map((beach: Beach) => {
return {
id: beach.id,
name: beach.name
};
}),
toArray()
);
}
getBeach(id: string): Observable<Beach> {
return from(data).pipe(filter((beach: Beach) => beach.id === id), delay(2000));
}
}
export class BeachesComponent implements OnInit {
content: Beach[] = [];
constructor(private beachService: BeachesService) {}
ngOnInit() {
this.beachService.getBeaches().subscribe((beaches: Beach[]) => {
this.content = beaches;
});
}
}
<h1>Beaches</h1>
<ul>
<li *ngFor="let beach of content">{{beach.name}}</li>
</ul>
ng g resolver pages/beaches/resolver/beaches
import { Injectable } from '@angular/core';
import {
Router, Resolve,
RouterStateSnapshot,
ActivatedRouteSnapshot
} from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class BeachesResolverService implements Resolve<boolean> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return of(true);
}
}
export class BeachesResolverService implements Resolve<boolean> {
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<Beach[]> | Promise<Beach[]> | Beach[] {
return this.beachService.getBeaches();
}
}
import { BeachesResolverService } from './resolver/beaches-resolver.service';
const routes: Routes = [{
path: '',
component: BeachesComponent,
resolve: {
beaches: BeachesResolverService
}
}];
import { BeachesResolverService } from './resolver/beaches-resolver.service';
@NgModule({
declarations: [BeachesComponent, DetailComponent],
imports: [CommonModule, BeachesRoutingModule],
providers: [BeachesResolverService]
})
import { ActivatedRoute } from '@angular/router';
export class BeachesComponent implements OnInit {
content: Beach[] = [];
constructor(private router: ActivatedRoute) {}
ngOnInit() {
this.router.data.subscribe(data => {
this.content = data.beaches;
});
}
}
export class BeachesComponent implements OnInit {
content$: Observable<Beach[]>;
constructor(private router: ActivatedRoute) {}
ngOnInit() {
this.content$ = this.router.data.pipe(pluck('beaches'));
}
}
<li *ngFor="let beach of content$ | async">{{beach.name}}</li>
ng g c pages/beaches/components/detail
import { DetailComponent } from './components/detail/detail.component';
const routes: Routes = [
{
path: '',
component: BeachesComponent,
resolve: {
beaches: BeachesResolverService
}
},
{
path: ':id',
component: DetailComponent
}
];
<h1>Beaches</h1>
<ul>
<li *ngFor="let beach of (content$ | async)">
<a [routerLink]="beach.id"> {{ beach.name }}</a>
</li>
</ul>
ng g resolver pages/beaches/resolver/beach-detail
import { Beach, BeachesService } from 'src/app/services/beaches.service';
export class BeachDetailResolverService {
constructor(private beachService: BeachesService) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<Beach> | Promise<Beach> | Beach {
return this.beachService.getBeach(route.params.id);
}
}
import { BeachDetailResolverService } from './resolver/beach-detail-resolver.service';
const routes: Routes = [
{
path: '',
component: BeachesComponent,
resolve: {
beaches: BeachesResolverService
}
},
{
path: ':id',
component: DetailComponent,
resolve: {
beach: BeachDetailResolverService
}
}
];
import { BeachDetailResolverService } from './resolver/beach-detail-resolver.service';
@NgModule({
declarations: [BeachesComponent, DetailComponent],
imports: [CommonModule, BeachesRoutingModule],
providers: [BeachesResolverService, BeachDetailResolverService]
})
import { ActivatedRoute } from '@angular/router';
export class DetailComponent implements OnInit {
beach$: Observable<Beach>;
constructor(private router: ActivatedRoute) {}
ngOnInit() {
this.beach$ = this.router.data.pipe(pluck('beach'));
}
}
<ng-container *ngIf="beach$ | async as beach">
<img [src]="beach.img" [alt]="beach.name">
<div class="content">
<h2>{{beach.name}}</h2>
<p>{{beach.desc}}</p>
</div>
</ng-container>
By Freddy Montes
Frontend Developer with a graphic design degree. UX/UI and Javascript crafter.