Web Understanding Router in Angular 6

前言:

這陣子在看 Reactive Extension (Rx) 和 Angular ,而 Angular 中常使用的 Router 內就有些 Observable Type 的 instance,因此就打算從這邊深入了解一下 Angular Router 的流程和 Observable 應用方式。

Router Navigation

Overview

![router.png]({{ site.url }}/assets/images/router.png)

首先,先來說一下大致流程,當使用者點擊設有 routerLink attribute 超連結時(或是在 component 裡面 trigger router navigation),設置的連結會先經過分析步驟,然後透過 Router 找出對應的 Component,再將此 Component 呈現在設置好 <router-outlet></router-outlet> 的地方。

建立 Router Service

網上有很多案例會教導如何設置 Component 的 Router,如下:

1import { RouterModule, Routes } from '@angular/router';
2const routes: Routes = [{path: 'content/:id', component: ArticleComponent}];
3@NgModule({
4  imports: [RouterModule.forRoot(routes)],
5  declarations: [ArticleComponent],
6})

RouterModule.forRoot(routes) 實際上會建立一個 Router 的 Singleton Service,裡面有 instance Router.RouterState 來管理目前的 Route 狀態,以及一些 methods 來讓使用者透過 Router 來進行 navigation。 Router class API

1class Router {
2  // 省略其他 methods or instance
3  get routerState: RouterState
4  navigateByUrl(url: string | UrlTree, extras: NavigationExtras = { skipLocationChange: false }): Promise<boolean>
5  navigate(commands: any[], extras: NavigationExtras = { skipLocationChange: false }): Promise<boolean>
6}

RouterState 樹狀結構與 ActivatedRoute 結點

RouterState

RouterState 是一個由 ActivedRoute 所組成的樹狀結構,以及 RouterStateSnapshot 存放當前的 Route 狀態。所以如果想要查看當前狀態,可以直接透過此 Service 進行查詢。

constructor(private router: Router){}
this.router.routerState.snapshot.url; /** 如果當前路徑在/Article/1,就會印出 /Article/1 */

RouterStateSnapshot Interface 而當使用者在進行 navigation 時,就是透過 RouterState 內的樹狀結構來找到對應的 Component。

ActivatedRoute

上面提到 RouterState 是個樹狀結構,而 ActivatedRoute 就是其中每個 node。 ActivatedRoute 除了有 routeConfig 配置要顯示 Component 和 matcher (負責驗證當前路徑是否匹配),更重要的是還帶有 paramsdata,讓頁面在切換到指定 Component 時,也能將資料帶往 Component。

1interface ActivatedRoute {
2  params: Observable<Params>,
3  data: Observable<Data>,
4  get paramMap: Observable<ParamMap>
5}

假設配置路徑為

1const routes = [{path : 'article/:id', component : ArticleComponent, data : {content : 'my article'}}]

當點擊以下路徑

1<a routerLink="/article/1"></a> 

就可以透過以下方式來取得對應資料

1constructor(private route: ActivatedRoute){}
2this.route.params.subscribe(params => params) // {id: 1}
3this.route.data.subscribe(data => data) // {content : 'my article'}

ActivatedRoute Injection

要注意的是,一個 ActivatedRoute 對應一個 Component,所以當從 Component Inject ActivatedRoute 時,他會引入關聯到此 Component 的 ActivatedRoute。如果你是想從 Service Inject ActivatedRoute,那要記得在 Component 註冊此 Service,假設是在 Module 層註冊 Service,那 Service 就會為 Singleton Service(意指 Application 內的 Component 都共用此 Service,當然如果你使用 Angular 6 的 provideredIn ,就是指 module 底下的 Component),並會 Inject 到 RouterState.root 這個 ActivatedRoute,這樣就不會是我們想要的結果。

1@Component({
2  selector: 'article',
3  templateUrl: 'article.html',
4  providers: [ArticleService] // 在此宣告 Service,讓 Service 是依賴在此 Component.
5})

Subscribe ActivatedRoute Service

最後,假設我們需要追蹤 ActivatedRoute.params 的變動,根據最新的 params 值做處理,很理所當然地,我們會使用 subscribe,例如:

1constructor(private route: ActivatedRoute){}
2
3ngOnInit{
4  this.route.params.subscribe(params => {
5    // deal with params
6  })
7}

如此一來,當 route.params 值發生改變時,我們就可以透過 subscribe 來追蹤。思考 observable 的做法,如果能進行 subscribe,那必定有個執行 next(params) 的地方。

1function advanceActivatedRoute(route: ActivatedRoute): void {
2  if (route.snapshot) {
3    if (!shallowEqual(currentSnapshot.params, nextSnapshot.params)) {
4      (<any>route.params).next(nextSnapshot.params);
5    }
6  }
7}

當在進行 navigate 時,會執行 runNavigate() ,然後依序處理 navigate 的流程,並執行 activateRoutes() 來觸發上面那段 next(params) 通知 observers。

如此一來,observer 就可以處理最新 params 了!

Reference

http://vsavkin.tumblr.com/post/145672529346/angular-router https://leanpub.com/router https://vsavkin.com/angular-router-understanding-router-state-7b5b95a12eab