angular2 學習筆記 ( Router 路由 )

更新 : 2017-11-04 

lazy load vs common chunk 

by default ng 會把共用的模組放入 common chunk 裡頭, 確保即使在 lazy load 的情況下, 模組都不會被載入 2 次 (不重複)

但是這樣的設定並不一定就最好的,因為 lazy load 的目的本來就是最小化的載入丫.

這只是一個平衡的問題. ng 視乎只給了 2 個極端的做法, 要嘛使用 common chunk 要嘛完全不要 common chunk 

ng build --prod --no-common-chunk 

 

 

更新 : 2017-10-18 

angular 的 router 有一個原則, 如果你觸發一個 <a href> 或則呼叫 router.navigate(...) 但是最終它發現 url 沒變動,那么什么不會發生, route event 統統沒有執行. 

還有另一個是當 url change 時 angular 不會輕易 rebuild component, 如果它的 path 依然是啟用的 angular 會保留它哦.

 

更新 : 2017-08-04

今天我才發現其實 Preload 不像我說的那樣, 我們可以在 preload 方法中把 load 這個方法保持起來. 

export class AppCustomPreloader implements PreloadingStrategy {
    loaders: Function[] = [];
    preload(route: Route, load: Function): Observable<any> {
        this.loaders.push(load);
        return Observable.of(null);
    }
}

然後在任何一個 component 注入 AppCustomePreloader 並呼叫 load 就可以了. 

ng 是很聰明的,如果 load() 執行時發現其模組已經載入了, 那么它並不會報錯. 所以鼓勵大家去使用它 .

 

更新 : 2017-04-05 

this.router.navigate(['../../'], { relativeTo: this.activatedRoute, queryParamsHandling: 'preserve' });
<a routerLink="../../" queryParamsHandling="preserve">

queryParamsHandling and preserveFragment 可以在移動 router 時保留當前的 queryParams and fragment 很方便哦。

queryParamsHandling 不只是可以 preserve, 還可以 merge 哦 

 

 

 

更新 : 2017-03-27


matcher 

如果我想自己寫 url 匹配, 我們可以通過 matcher 

export function matcher(segments: UrlSegment[], group: UrlSegmentGroup, route: Route) {   
    //判斷
    return {
        consumed: segments.slice(1), //如果你的 route 還有 child 的話,這裡要注意,只放入你所匹配到的範圍,後面的交給 child 去判斷. 
        posParams: { Id: segments[1] } // 傳入 params, url matrix 等等
    }
}

@NgModule({
    imports: [RouterModule.forChild([ 
         {
             matcher : matcher, //幫發放放進來, 這裡不要使用匿名方法或則箭頭函式哦, aot 不過
             component : FirstComponent,
             children : []
         }
    ])],
    exports: [RouterModule],
})
export class DebugRoutingModule { }

 

 

 

更新 2017-03-05 

preloading module 

lazy load 的好處是 first load 很快, 因為很多 module 都沒有 load 嘛, 但是後續的操作就會變成卡卡的, 因為後來要 load 嘛. 

2.1 開始 ng 支援 preloading 的概念. 就是通過 lazyload 讓你 firstload 很快之後, 立馬去預先載入其它的 module. 

比如你的 first page 是一個登入介面, 使用者就可以很快看到頁面,讓後乘著客戶在輸入密碼時,你偷偷去載入後續會用到的模組。這樣客戶接下來的操作就不會卡卡的了. 

這聽上去不錯哦. 

要注意的是, ng 並不太智慧, 它不會等到 browser idle 的時候才去載入. 它會馬上去載入. 

如果你的首頁有很多圖片或者視屏, ng 不會等待這些圖片視屏載入完了才去載入其它模組, 它會馬上去載入. 這可能會造成一些麻煩 (因專案而定, 自己做平衡哦)

@Injectable()
export class MyPreloadingStrategy implements PreloadingStrategy {
    constructor(private route : ActivatedRoute, private router : Router) {
    //可以注入 router route 任何 service 來幫助我們判斷也不要 preload 
    }
    preload(route: Route, load: () => Observable<any>): Observable<any> {
     // ng 會把每一個 lazyload 的 module 丟進來這個函式, 問問你是否要 preload, 如果要, 你就返回 load() 不要 preload 的話就返回 Observable.of(null);
return (true) ? load() : Observable.of(null); } } @NgModule({ imports: [RouterModule.forRoot([ { path: 'home', loadChildren: "app/+home/home.module#HomeModule" }, { path: "about", loadChildren: "app/+about/about.module#AboutModule" }, { path: "contact", loadChildren: "app/+contact/contact.module#ContactModule" }, { path: "", redirectTo: "home", pathMatch: "full" } ], { preloadingStrategy: MyPreloadingStrategy })], // { preloadingStrategy: PreloadAllModules } <--ng 自帶的
exports: [RouterModule], providers: [MyPreloadingStrategy] }) export class AppRoutingModule { }

只要在 forRoot 裡新增 preloadingStrategy 就可以了. 上面我用了一個自定義的處理, 如果你想簡單的表示全部都預載入的話,可以使用 ng 提供的 PreloadAllModules

 

更新 2017-01-29 

提醒 : 

路由是有順序的, 在用 import 特性模組時, 位置要留意. 

例如, 如果你 app-routing 最後是處理 404 

但是在 app-module 卻把 routing 限於特性模組 IdentityModule, 那么 IdentityModule 的 routing 就進不去了。因為已經被匹配掉了.

 

2016-08-26 

參考 : 

https://angular.cn/docs/ts/latest/guide/router.html#!#can-activate-guard

https://angular.cn/docs/ts/latest/api/    -@angular/router 部分

 

ng 路由的概念和遊覽器類似, 和 ui-router 也類似, 下面會把具體功能逐一解釋 

 

1. html5 和 hash # 

ng 預設模式是 html5, 在開發階段我們喜歡使用 hash 模式, 這樣可以不用部署伺服器. 

要從 html5 轉換成 hash 只要做一個小設定 : 

(update:用 angular cli 開發的話,不需要 hash 模式了.)

 

2.child 

和 ui-view 一樣 ng 也支援巢狀 

就是一個路由的元件模板內又有另一個路由元件 

const appRoutes: Routes = [   
    {
        path: "",
        redirectTo: "home",
        pathMatch: "full"       
    },
    {
        path: "home",  
        component: TopViewComponent, //view 內也有 <router-outlet>
        children: [
            {
                path: ""  //如果沒有設定一個空路由的話, "/home" 會報錯, 一定要 "/home/detail" 才行. 
            },
            {
                path: "detail",
                component: FirstChildViewComponent            
            }            
        ]
    }
];

 

3. 獲取 params ( params 是 Matrix Url 和 :Id , 要拿 search 的話用 queryParams )

class TestComponent implements OnInit, OnDestroy {
    //home/xx
    private sub : Subscription;
    constructor(private route: ActivatedRoute) { }    
    ngOnInit()
    {
        //監聽變化
        this.sub = this.route.params.subscribe(params => {
            console.log(params); //{ id : "xx" }
        });
        //如果只是要 get 一次 value, 用快照
        console.log(this.route.snapshot.params); //{ id : "xx" }
    }
    ngOnDestroy()
    {
        this.sub.unsubscribe(); //記得要取消訂閱, 防止記憶體洩露 (更新 : 其實 ActivatedRoute 可以不需要 unsubscribe,這一個 ng 會智慧處理,不過養成取消訂閱的習慣也是很好的)
    }
}

 

4. 導航

導航有個重要概念, 類似於遊覽器, 當我們表示導航時 ../path, /path, path 它是配合當下的區域而做出相對反應的. 記得是 path+區域 哦.

<a [routerLink]="['data',{ key : 'value' }]" [queryParams]="{ name : 'keatkeat' }" fragment="someWhere" >go child</a>

 

export class TopViewComponent {
    constructor(private router: Router, private route: ActivatedRoute) {
        console.clear();
    }
    click(): void {
        this.router.navigate(
            ["data", { key: "value" }], //data 是 child path, {key : "value"} 是 Matrix Url (矩陣 URL) 長這樣 /data;key=value
            {
                relativeTo: this.route, //表示從當前route開始, 這個只有在 path not start with / 的情況下需要放. 
                //一般的 queryParams, 這裡只能 override 整個物件, 如果你只是想新增一個的話,你必須自己實現保留之前的全部.         
                queryParams: {
                    'name': "keatkeat" // ng 會對值呼叫 toString + encode 才放入 url 中, 解析時會 decode, 然後我們自己把 str convert to 我們要的值
                },
                //#座標
                fragment: "someWhere",
        replaceUrl : true //有時候我們希望 replace history 而不是 push history } );      //redirect by url let redirectUrl = this.route.snapshot.queryParams["redirectUrl"]; if (redirectUrl != undefined) { this.router.navigateByUrl(redirectUrl); } else { this.router.navigate(["/admin"]); } } }

 

大概長這樣, 最終個結果 : /home/data;key=value?name=keatkeat#someWhere

通過指令 routerLink , 或則使用程式碼都可以 (寫法有一點點的不同, 看上面對比一下吧)

導航使用的是陣列, ["path1","path2",{ matrix : "xx" },"path3"],  你也不一定要一個 path 一個格子, ['/debug/a/b','c'] 也是 work 的

和遊覽器類似

"/path" 表示從 root 開始

"../path" 表示從當前route往上(parent)

"path" 表示從當前往下(child) 

這裡有個關鍵概念, 在不同的 component 獲取到的 this.route 是不同的, 元件和 route 是配合使用的 

比如上面 click() 方法,如果你放在另一個元件,結果就不同了,this.route 會隨著元件和改變. 

沒有 route name 的概念 (之前好像是有,不知道是不是改了..沒找到../.\), 就是用 path 來操作. 

matrix url 和 params 獲取的手法是一樣的. 他的好處就是不需要把所有子孫層頁面的引數都放到 params 中,放到 matrix url 才對嘛. 

提醒 : { path : "product?name&age" } 註冊 route 的時候不要定義 queryParam. ?name&age 刪掉 (ui-router need, ng not)

 

 

5. 攔截進出

通常進入時需要認證身份,出去時需要提醒儲存資料. 

ng 為我們提供了攔截點 

{
    path: ":id",
    component: TestComponent,
    data: {
        title : "test"
    },
    canActivate: [BlockIn],  //進入
    canDeactivate : [BlockOut] //出去
}    

BlockIn, BlockOut 分別是2個服務 

@Injectable()
export class BlockIn implements CanActivate {
    constructor(
        private currentRoute: ActivatedRoute,
        private router: Router,
    ) { }
    canActivate(nextRoute: ActivatedRouteSnapshot, nextState: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
        //this.currentRoute vs nextRoute some logiz 
        let nextUrl = nextState.url;
        let currentUrl = this.router.url;       
        return new Promise<Boolean>((resolve, reject) => {
            setTimeout(() => {
                resolve(true);
            },5000);           
        }); 
    }
}

 

實現了 CanActivate 介面方法, 裡頭我們可以獲取到即將進入的路由, 這樣就足夠讓我們做驗證了, 途中如果要跳轉頁是可以得哦..

@Injectable()
export class BlockOut implements CanDeactivate<TestComponent> {

    canDeactivate(
        component: TestComponent,
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
    ): Observable<boolean> | Promise<boolean> | boolean {
        console.log("leave");
        return true;
    }
}

CanDeactivate 還多了一個 Component, 讓我們在切出的時候可以呼叫 component 內容來做檢查或者儲存資料等等, 很方便呢. 

還有一個叫 CanActivateChild, 依據子層來決定能否訪問... 它和 CanActivate 的區別是

canActivate(nextRoute: ActivatedRouteSnapshot) vs canActivateChild(childRoute: ActivatedRouteSnapshot) 

有點像 dom 事件的 event.target vs event.currenttarget 的概念.

 

6. resolve 

@Injectable()
export class DataResolve implements Resolve<String> {
    constructor(private router: Router) { }
    resolve(route: ActivatedRouteSnapshot): Observable<any> | Promise<any> | any {        
        console.log("masuk");
        if ("xx" === "xx") {
            return "xx";
        }
        else
        {
            this.router.navigate(['/someWhere']); //隨時可以跳轉哦
        } 
    }
}
{
    path: "home",
    component: TopViewComponent,
    resolve: {
        resolveData: DataResolve
    },
}

註冊在 route 裡 

providers: [appRoutingProviders, Title, BlockIn, BlockOut, DataResolve],

還要記得註冊服務哦 

ngOnInit()
{
    console.log("here");
    console.log(this.route.snapshot.data);    
}

在 onInit 裡面就可以使用啦. 

提醒 : 和 ui-router 不同的時, ng 的 resolve 和 data 是不會滲透進子路由的,但是我們在子路由裡呼叫 this.route.parent.... 來獲取我們想要的資料. 

 

7. set web browser title

這個和 router 沒有直接關係,只是我們通常會在還 route 時改動 browser title 所以記入在這裡吧

ng 提供了一個 service 來處理這個title

import { BrowserModule, Title } from '@angular/platform-browser';

providers: [appRoutingProviders, Title, BlockIn, BlockOut, DataResolve]

constructor(private route: ActivatedRoute, private titleService: Title) {
    this.titleService.setTitle("data");
}

註冊 & 注入 service 就可以用了

 

8.auxiliary routes / multi view

參考 : 

    -http://blog.angular-university.io/angular2-router/
    -https://yakovfain.com/2016/08/16/angular-2-working-with-auxiliary-routes/
    -http://stackoverflow.com/questions/38572370/how-to-specify-auxiliary-component-for-child-route-in-angular2-router3
    -http://vsavkin.tumblr.com/post/146722301646/angular-router-empty-paths-componentless-routes 
 
ng 的 multi view 和 ui-router 是不同概念 
ng 的 multi view 是指 多個<router-outlet name="secondOutlet" > 在元件中 
每一個 router-outlet 都有屬於自己的路由管理, 如果你只用一個, 那么它就是 <router-outlet name="primary"> 而 url 就是 browser url 
如果你使用多個那么 ng 會把多個 url 結合成一個 放到 browser url 中 
它看起來是這樣的 /home/(detail//popup:popup)(chat:chat) 對應 /path1/(childPath1//childSecondOutletName:childSecondOutletPath)(mainSecondOutletName:mainSecondOutletPath)
看上去很詭異吧... 它還可以無限巢狀哦 ... 嘻嘻
那它表示的路由結構是這樣的 : (全部被啟用) 
const appRoutes: Routes = [    
    {
        path: "home",  
        children: [
            {
                path: "detail",          
            },
            {
                path: "popup", 
                outlet : "popup"
            }
        ]
    },
    {
        path: "chat", 
        outlet : "chat"
    }        
];

結構提醒 : 

根層是 /home(chat:chat) 而不是 /(home//chat:chat)

子層是 /home/(detail//popup:popup) 而不是 /home/detail(popup:popup) 

要留意哦.

// create /team/33/(user/11//aux:chat)
router.createUrlTree(['/team', 33, {outlets: {primary: 'user/11', right: 'chat'}}]);
// remove the right secondary node
router.createUrlTree(['/team', 33, {outlets: {primary: 'user/11', right: null}}]);

生成的方式. 

 

9.非同步載入特性模組

首先特性模組和主模組的 routing 設定不同 

一個用 .forRoot, 一個用 .forChild 方法

export const routing: ModuleWithProviders = RouterModule.forChild(routes);

要非同步載入特性模組的話,非常簡單. 

在主路由填入 loadChildren 屬性,值是模組的路徑#類的名字 

const appRoutes: Routes = [   
    {
        path: "",
        redirectTo: "/home",        
        pathMatch: "full"       
    },
    {
        path: "home",
        component: HomeComponent
    },
    {
        path: "product",
        loadChildren: "app/product/product.module#ProductModule" //ProductModule 是類的名字, 如果是用 export default 的話,這裡可以不需要表明
    }
];

在特性模組的路由, 把 path 設定成空

const routes: Routes = [       
    {
        path: "",
        component: ProductComponent
    }
];

這樣就可以啦.

 

小記:

1.Router : 用於 redirect 操作

2.ActivateRoute : 用於獲取 data, params 等等

3.Route : 就是我們每次註冊時寫的資料咯, 裡面有 data, path 哦

 

 

 

 
 
 
 
 
 

 

關鍵詞:router path route 一個 可以 ng this home url 如果

相關推薦:

預設路由

用Vue.js來開發一個電影App的前端部分

全面解析JavaScript的Backbone.js框架中的Router路由

vue路由vue-router的使用

關於簡單動態路由協議配置,注入,路由重分佈

react-router路由

vue-router

Angular實現多標籤頁效果(路由重用)

淺入淺出 react-router v4 實現巢狀路由

vue+mui+html5+ plus開發的混合應用底部導航的顯示與隱藏