ng-book札记——路由
路由的作用是分隔应用为不同的区块,每个区块基于匹配当前URL的规则。
路由可以分为服务端与客户端两种,服务端以Express.js为例:
var express = require('express'); var router = express.Router(); // define the about route router.get('/about', function(req, res) { res.send('About us'); });
服务端接收请求并路由至一个控制器(controller),控制器执行指定的操作(action)。
客户端的路由在概念上与服务端相似,其好处是不需要每次URL地址变化都将路由请求发送至服务端。
客户端路由有两种实现方式:使用传统的锚(anchor)标签与HTML5客户端路由。第一种方式也被称为hash-based路由,URL的形式如这般:http://something/#/about
。第二种依赖HTML5的history.pushState
方法,缺点是老旧的浏览器不支持这种方式,以及服务器也必须支持基于HTML5的路由。Angular官方推荐的是后者。
使用Angular路由的方法,首先要导入相关类库:
import { RouterModule, Routes } from '@angular/router';
再配置路由规则:
const routes: Routes = [ // basic routes { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, { path: 'about', component: AboutComponent }, { path: 'contact', component: ContactComponent }, { path: 'contactus', redirectTo: 'contact' } ]
path指定路由所要处理的URL,component绑定相关的组件,redirectTo重定向至已知的路由。
最后在NgModule中引入RouterModule模块及预设的路由:
imports: [ BrowserModule, FormsModule, HttpModule, RouterModule.forRoot(routes), // <-- routes // added this for our child module ProductsModule ]
Angular默认的路由策略是PathLocationStrategy,即基于HTML5的路由。如果想使用HashLocationStrategy,需要在代码里额外申明。
providers: [ { provide: LocationStrategy, useClass: HashLocationStrategy } ]
路由上可以带参数,通过/route/:param
的形式。不过这种情况下需要导入ActivatedRoute。
import { ActivatedRoute } from '@angular/router';
const routes: Routes = [ { path: 'product/:id', component: ProductComponent } ];
export class ProductComponent { id: string; constructor(private route: ActivatedRoute) { route.params.subscribe(params => { this.id = params['id']; }); } }
想在页面中添加跳转链接的话,可以使用[routerLink]指令:
<div class="page-header"> <div class="container"> <h1>Router Sample</h1> <div class="navLinks"> <a [routerLink]="['/home']">Home</a> <a [routerLink]="['/about']">About Us</a> <a [routerLink]="['/contact']">Contact Us</a> | <a [routerLink]="['/products']">Products</a> <a [routerLink]="['/login']">Login</a> <a [routerLink]="['/protected']">Protected</a> </div> </div> </div>
而如果想要用模板页面的话,则需要
<div id="content"> <div class="container"> <router-outlet></router-outlet> </div> </div>
页面中的router-outlet元素即是每个路由绑定的组件所渲染内容的位置。
复杂的页面可能还会需要用到嵌套路由:
const routes: Routes = [ //nested { path: 'products', component: ProductsComponent, chilren: [ { path: '', redirectTo: 'main', pathMatch: 'full' }, { path: 'main', component: MainComponent }, { path: 'more-info', component: MoreInfoComponent }, { path: ':id', component: ProductComponent }, ] } ]
路由的建立是经由相对路径来构成,所以需要有配置其基础路径位置的地方,一般这个配置会放在index.html页面的base元素中。
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Routing</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <app-root>Loading...</app-root> </body> </html>
最常见的写法就是<base href="/">
。
同时还可以在NgModule中以代码实现,两者效果等效:
providers: [ { provide: APP_BASE_HREF, useValue: '/' } // <--- this right here ]
切换路由时可能会有要求额外的处理工作,比如认证。这种场景下,路由的构子可以派上用处。
import { Injectable } from '@angular/core'; @Injectable() export class AuthService { login(user: string, password: string): boolean { if (user === 'user' && password === 'password') { localStorage.setItem('username', user); return true; } return false; } logout(): any { localStorage.removeItem('username'); } getUser(): any { return localStorage.getItem('username'); } isLoggedIn(): boolean { return this.getUser() !== null; } }
export const AUTH_PROVIDERS: Array<any> = [ { provide: AuthService, useClass: AuthService } ]; import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { AuthService } from './auth.service'; @Injectable() export class LoggedInGuard implements CanActivate { constructor(private authService: AuthService) {} canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { const isLoggedIn = this.authService.isLoggedIn(); console.log('canActivate', isLoggedIn); return isLoggedIn; } }
import { AUTH_PROVIDERS } from './auth.service'; import { LoggedInGuard } from './logged-in.guard'; const routes: Routes = [ { path: 'protected', component: ProtectedComponent, canActivate: [ LoggedInGuard ] }, ];
上述例子中,当路由至'protected'地址时,'LoggedInGuard'处理类会进入路由调用环路,依据是否已登陆这一信息来决定此次路由是否有效。