Routes = > app.module.ts ```typescript= import { Routes } from '@angular/router'; // declare routes const appRoutes: Routes =[ { path: '', component: HomeComponent } { path: 'users', component: UsersComponent } { path: 'servers', component: ServersComponent } //in case we want the opening page to redirect to servers - we will do: { path: '', redirectTo: '/servers', pathMatch: 'full' } // pathMatch must be added or else this cause an error: // because '' matches every route ] // register the routes to our angular app imports: [ RouterModule.forRoot(appRoutes) ] ``` when `localhost:4200/users` reached, `UserComponent` is called this register the routes to our angular app > template.html ```htmlmixed= <li role="presentation"> <a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" > Home </a> </li> <li role="presentation"> <a routerLink="/users" routerLinkActive="active" > Users </a> </li> <li role="presentation"> <a [routerLink]="['/users','10','Anna']" // `/users/10/Anna` routerLinkActive="active" > Users </a> </li> <router-outlet> </router-outlet> ``` `routerLink` - Avoid reloading on each route activation `routerLinkActive` - Mark as active only when contains this path `routerLinkActiveOptions` - only at this exact path `/` mark as active Trigger route from within our code - load a route automatically on button click - 1. Inject router into the component 2. Call a function from the button ```htmlmixed= <button class="btn btn-primary" (click)="onLoadServers"> Load Servers </button> ``` ```typescript= import { Router, ActivatedRoute } from `@angular/router`; constructor( private router: Router private route: ActivatedRoute) { } onLoadServers() { this.router.navigate(['/servers']); // absolute path. navigate takes an array of elements in the path this.router.navigate(['servers'], {relativeTo: this.route}); // let navigate know on what route it is currently in // reload the page // results with /servers/servers } ``` Passing parameter to Routes - > app.module ```typescript= { path: 'users/:id/:name', component: UsersComponent } // dynamic id, name ``` > app.component.ts ```typescript= ngOnInit() { this.user = { id: this.route.snapshot.params[id], name: this.route.snapshot.params[name] // retrieve id and name into component dynamically from route path } // Or with + to convert string from route to number const id =+this.route.snapshot.params['id']; this.user = this.UserService.getUser(id); // observer - deal with asynchronous events and // be fired whenever new data is sent to this route // not executed on ngOnInit this.route.params.subscribe( (params: Params) => { this.user.id = params['id']; this.user.name = params['name']; } ); } ``` * If we add our own observer --> we need to unsubscribe via onDistroy (on course) Query parameters, Fragment (Pass & retrieve) - `localhost:4200/users/1/Max?mode=editing#loading` Route to an edit page and decide if allowed ### Pass ```typescript { path: 'servers/:id/edit', component: EditServerComponent } ``` ```htmlmixed <a [routerLink]="['/servers', server.id,'edit']" [queryParams]="{allowEdit: '1'}" fragment="loading" routerLinkActive="active" > Edit Server </a> </li> ``` ### Retrieve > app.component.ts > ```typescript this.route.snapshot.queryParams; this.route.queryParams.subscribe(); ``` ### Nested Router load menu next to current menu ```typescript= const appRoutes: Routes =[ { path: 'servers', component: ServersComponent, children: [ { path: '/:id', component: ServersComponent } { path: '/:id/edit', component: EditServersComponent } ] } ] ``` ```htmlmixed= <router-outlet></router-outlet> // instead of app-servers, app-servers-edit components ``` Wildcard Route - For Routes we don't have - 404/Redirect page ```typescript= { path: 'page-not-found', component: PageNotFoundComponent } { path: '**', redirectTo: '/page-not-found' } ``` `**` always put last in Routes Cleaner code - Creatring a router mdoule and remove routing from app.module > app.module ```typescript= import { AppRoutingModule } from './app-routing.module.ts'; imports: [ AppRoutingModule ] ``` > app-routing.module.ts ```typescript= import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import {HomeComponent } from '/home/home.component'; // import all Components const appRoutes: Routes =[ { path: '', component: HomeComponent } { path: 'users', component: UsersComponent } { path: 'servers', component: ServersComponent } ] @NgModule({ imports: [ RouterModule.forRoot(appRoutes) ], exports: [RouterModule] }) export class AppRoutingModule { } ``` Guards - Enables access to routes only for logined users > auth-guard.service.ts (dont forget importing to `app.module.ts`) ```typescript= import { CanActivate, ActivatedRouterSnapshot, RouterStateSanpshot } from `@angular/core`; import { AuthService } from './auth.service'; @Injectable() export class AuthGuard implements CanActivate, CanActivateChild { constructor( private authService: AuthService private router: Router) {} canActivate(route: ActivateRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { return his.authService.isAuthenticated().then( (authenticated: boolen) => { if (authenticated) return true; else this.router.navigate(['/']); // navigate home or just return false }); } // To guard only the childs of this route canActivateChild(route: ActivateRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { return this.canActivate(route,state); } ``` > auth.service.ts (dummy service - in realworld, here we access the server) ```typescript= export class AuthService { loggedIn = false; isAuthenticated() { const promise = new Promise( (resolve, reject) => { setTimeout( () => { resolve(this.loggedIn); }, 800); //ms } ); return promise; } login(){ this.loggedIn = true; } logout(){ this.loggedIn = false; } } ``` > app-routing.module.ts ```typescript= // Inside the array, enter all guards should be used on this route { path: 'users', canActivate: [AuthGuard], component: UsersComponent } // Guard childs, add this in parent { path: 'users', canActivateChild: [AuthGuard], component: UsersComponent } ``` Enable Login with AuthService - > home.component.html ```htmlmixed= <button class="btn btn-default" (click)="onLogin()"> Login </button> <button class="btn btn-default" (click)="onLogout()"> Logout </button> ``` > home.component.ts ```typescript= constructor(... private authService: AuthService) { } onLogin() { this.authService.login(); } onLogout() { this.authService.logout(); } ``` no visual indication yet, but content is now available only for logged in users. Control wheter you're allowed to leave a Route - like leaving without saving/submitting. > edit.server.ts ```typescript= import { Observeable } from 'rxjs/Observable'; export class EditServerComponent implements CanComponentDeactivate { allowEdit = false; changesSaved = false; onUpdateServer(){ this.changesSaved = true; } canDeactivate() { if (!this.allowEdit) return true; // onInit we binded serverName with server.name // serverName: bound property to input // server.name: previous value if (this.serverName !== this.server.name || this.serverStatus !== this.server.status && !this.changesSaved) return confirm('Do you want to discard changes?'); else return true; } } ``` **Add a new File** > can-deactivate-guard.service.ts ```typescript= export interface CanComponentDeactivate { canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean; } //interface, therefore needs to be implmeneted. export class CanDeactivateGuard implements CanDeacivate<CanComponentDeactivate>{ canDeactivate(component: CanComponentDeactivate, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): // nextState? - Optional argument // Function's returned value is the below Observable<boolean> | Promise<boolean> | boolean { return component.canDeactivate(); } } ``` > app-routing.module.ts ```typescript= { path: 'users', component: UsersComponent, canDeactivate: [CanDeactivateGuard]} ``` and don't forget to provide service in `app.module.ts` Get dynamic data to a Route - (Previeus chapter deals with static data) * Resolver (service) - run some code before the Route is rendered * Preload some data from the server before accesing/displaying route * Alternative - Load instantly and present a spinner. * Create a new service to retrieve data with a resolver instead of via params. * Important when using asynchronis data > server-resolver.service.ts ```typescript= import { Resolve } from '@angular/router'; interface Server { id: number; name: string; status: string; } @Injectable export class ServerResolver implements Resolve<Server> { constructor(private serversService: ServersService) resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Server> | Promise<Server> | Server { //'+' - cast to integer return this.serversService.getServer(+route.params['id']) } } ``` > app-routing.module.ts ```typescript= { path: 'users', component: UsersComponent, resolve: {server: ServerResolver} } ``` this will map the data it gives us back > server.component.ts ```typescript= ngOnInit() { this.route.data.subscribe( (data: Data) => { this.server = data['server'] } ); } ``` Hash Configuration - On real server, the URL is parsed by the web server first, not by angular. Adding `#` to the URL - `localhost:4200/#/servers` informs the server to parse only the first part of the URL `localhost:4200/` and let Angular parse the rest `/servers` > app-routing.module.ts ```typescript= imports: [ RouterModule.forRoot(appRoutes, {useHash: true}) ] ``` <!-- <hr> --> ### Avoid reloading the page when clicking on elements 1. Remove `href="#"` - this calls the web server 2. Add ```CSS style="cursor: pointer;" ``` to view clickable when hovering over the element. ### New item / Edit item page > recipe-edit.component.ts ```typescript= export class RecipeEditComponent implements OnInit { id: number; editMode = false; constructor(private route: ActivatedRoute) { } ngOnInit() { this.route.params .subscribe( (params: Params) => { // retrieve id dynamically this.id = +params['id']; // if route has id parameter - then we are in Edit mode // else: we are creating a New item this.editMode = params['id'] != null; } ); } } ``` > app-routing.module.ts ```typescript= { path: 'new', component: RecipeEditComponent }, { path: ':id', component: RecipeDetailComponent }, ``` * Must put `new` before `:id` to avoid error - Routing does first match prefix (`localhost:4200/recipe/new` will fail because `id=new` not identified as valid id) * Both `New` and `Edit` are handled within same component - can be tell apart by `editMode` property.