Angular2以組件化的視角來看待web應用,使用Angular2開發的web應用,就是一棵組件樹。組件大致分為兩類:一類是如list、table這種通放之四海而皆准的通用組件,一類是專為業務開發的業務組件。實際開發中大部分時間我們都需要處理業務組件。對於SPA應用來說,一個通用的問題就是如何控制頁面的切換,解決這個問題的通用方法就是利用路由器來實現。
路由配置
現在我們先撇開Angular2來看看通用的路由器模型。通常來講SPA應用需要路由配置信息:
[ { path: '', pathMatch: 'full', redirectTo: '/inbox' }, { path: ':folder', children: [ { path: '', component: ConversationsCmp }, { path: ':id', component: ConversationCmp, children: [ { path: 'messages', component: MessagesCmp }, { path: 'messages/:id', component: MessageCmp } ] } ] }, { path: 'compose', component: ComposeCmp, outlet: 'popup' }, { path: 'message/:id', component: PopupMessageCmp, outlet: 'popup' } ]
這個配置信息定義了應用的潛在路由狀態(Router State)。一個路由狀態代表了一份組件布置信息。 現在我們換一個視角來看這份配置:
在這棵配置樹中,每一個節點就是一個路由,它對應了一個組件。
路由狀態
在路由樹這種視角下,每一個路由狀態就是配置樹的一棵子樹。下圖中的路由狀態下,最終被激活的組件是ConversationCmp:
導航
路由器的首要任務就是控制在不同路由狀態之間導航以及更新組件樹。如下圖所示,當我們導航到另一個頁面時,路由狀態也會發生改變,隨之頁面上顯示的組件也跟隨變化。
到此為止路由器的基本模型已經介紹完畢,下面我們來看一下Angular2中的路由模型。
Angular2路由處理流程
Angular2對待一個URL的處理流程為:
- 應用重定向
- 識別路由狀態
- 應用哨兵與傳遞數據
- 激活對應組件
在所有步驟之前路由器要進行url格式解析,這部分內容不是本文重點,請大家自行查看其它資料。
重定向
假設我們訪問的地址是:http://hostname/inbox/33/message/44。路由器首先根據配置規則:
{ path: ‘’, pathMatch: ‘full’, redirectTo: ‘/inbox’ }
來判斷是否需要重定向,如果我們的url是http://hostname/此時,就是重定向到http://hostname/inbox,根據配置規則:folder,這時候被激活的組件就是ConversationComp。但現在我們的url是http://hostname/inbox/33/message/44,所以不會發生重定向。
識別路由狀態
接下來路由器會為這個URL分發一個路由狀態。根據配置規則
{ path: ':folder', children: [ { path: '', component: ConversationsCmp }, { path: ':id', component: ConversationCmp, children: [ { path: 'messages', component: MessagesCmp }, { path: 'messages/:id', component: MessageCmp } ] } ] }
/inbox/33/message/44首先匹配:folder,對應組件為ConversationCmp,而后進入子配置,'message/:id',MessageCmp組件被激活。
根據上圖的狀態樹,我們可以看出MessageCmp與ConversationCmp對應的路由狀態。與此同時一個被稱為激活路由(ActivatedRoute)的對象將被創建,並可以在MessageCmp訪問到,通過ActivatedRoute我們可以拿到它的routerState屬性,通過路由狀態我們可以拿到具體參數如id對應的44。從此也可以看出拿到父級參數id(33)就必須訪問父級的路由狀態。
ngOnInit() { this.sub = this.router.routerState.parent(this.route).params.subscribe(params => { this.parentRouteId = +params["id"]; }); }
哨兵與分發數據
哨兵的作用是判斷是否允許應用在不同狀態間進行切換,比如:如果用戶沒有登陸就不允許進入Message頁面。哨兵可以用來判斷是否允許進入本路由狀態,是否允許離開本路由狀態。下例中的CanActivate用來判斷是否允許進入,這個服務類需要繼承CanActivate接口。
import { AuthGuard } from '../auth-guard.service'; const adminRoutes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [AuthGuard], children: [ { path: '', children: [ { path: 'crises', component: ManageCrisesComponent }, { path: 'heroes', component: ManageHeroesComponent }, { path: '', component: AdminDashboardComponent } ], } ] } ]; export const adminRouting: ModuleWithProviders = RouterModule.forChild(adminRoutes);
import { Injectable } from '@angular/core'; import { CanActivate } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { canActivate() { console.log('AuthGuard#canActivate called'); return true; } }
哨兵內容涉及到另一個部分知識,所以我會把他放到下一篇文章中。
Angular2的路由器允許我們在進入組件中拿到除當前路由參數之外的其他信息。在路由配置中使用resolve屬性指定一個數據分發器。
[ { path: ':folder', children: [ { path: '', component: ConversationsCmp, resolve: { conversations: ConversationsResolver } } ] } ]
數據分發器需要繼承DataResolver接口:
@Injectable() class ConversationsResolver implements DataResolver { constructor(private repo: ConversationsRepo, private currentUser: User) {} resolve(route: ActivatedRouteSnapshot, state: RouteStateSnapshot): Promise<Conversation[]> { return this.repo.fetchAll(route.params['folder'], this.currentUser); } }
還需要把這個數據分發器加入到module的Providers中:
@NgModule({ //... providers: [ConversationsResolver], bootstrap: [MailAppCmp] }) class MailModule { } platformBrowserDynamic().bootstrapModule(MailModule);
而后我們在組件中就可以通過ActivatedRoute來訪問分發數據了。
@Component({ template: ` <conversation *ngFor="let c of conversations | async"></conversation> ` }) class ConversationsCmp { conversations: Observable<Conversation[]>; constructor(route: ActivatedRoute) { this.conversations = route.data.pluck('conversations'); } }
激活組件
此時路由器根據路由狀態來實例化組件並把他們放到合適的路由組出發點上。
@Component({ template: ` ... <router-outlet></router-outlet> ... <router-outlet name="popup"></router-outlet> ` }) class MailAppCmp { }
如‘/inbox/33/message/44(popup:compose)’,首先實例化ConversationCmp放到主<router-outlet>中,然后實例化MessageCmp放到name為popup的<Router-outlet>中。
現在路由器對URL的解析過程完畢。但是如果用戶想從MessageCmp中跳轉到別的路由狀態該如何做呢?Angular2提供了兩種方式。
一種是通過router.navigate方法來導航:
@Component({...}) class MessageCmp { private id: string; constructor(private route: ActivatedRoute, private router: Router) { route.params.subscribe(_ => this.id = _.id); } openPopup(e) { this.router.navigate([{outlets: {popup: ['message', this.id]}}]).then(_ => { // navigation is done }); } }
一種是利用router-link方式:
@Component({ template: ` <a [routerLink]="['/', {outlets: {popup: ['message', this.id]}}]">Edit</a> ` }) class MessageCmp { private id: string; constructor(private route: ActivatedRoute) { route.params.subscribe(_ => this.id = _.id); } }
參考資料:
http://stackoverflow.com/questions/34500147/angular-2-getting-routeparams-from-parent-component