更新 : 2019-11-21
我們經常喜歡用 empty string 來做 default router path
比如 2 個 tabs
<nav mat-tab-nav-bar> <a mat-tab-link [routerLink]="['./']" routerLinkActive [routerLinkActiveOptions]="{exact: true}" #rla1="routerLinkActive" [active]="rla1.isActive"> Test A {{ rla1.isActive }} </a> <a mat-tab-link [routerLink]="['test-b']" routerLinkActive #rla2="routerLinkActive" [active]="rla2.isActive"> Test B {{ rla2.isActive }} </a> </nav>
要留意第一個如果忘記放 exact: true, 那么第二個被匹配到的時候它也會 active 哦,所以使用 empty path + router active 時要記得了
注意 : router link 配 empty string 還要有 relation 寫法是
<a routerLink=“./” >go</a>
更新: 2019-08-05
之前都沒有什么用到 matrix url, 因為項目難度不大,用 queryparams 挺好的,但是越來月復雜后開始感覺到 matrix 的好了.
要改動 matrix 比較難, 沒有類似 'merge' 啊等等的東西.
常用的方式是
<a [routerLink]="['../',{}]" >gogo</a>
不過要留意哦, root path 就不可以用這招了,root path should not have matrix.
然后說到 ../ 我就氣!
https://github.com/angular/angular/issues/13011 這個 issue 幾年前我就 like 了. 然后我也不記得那時候怎樣閃掉了.
今天又給我與到... 我根本就不記得有過這種事情了... n 年前... 盡然沒有 fix !!! 什么鬼嘛
看了幾眼原來是有 fix 的.. 只是 fix 的不全面而已
relativeLinkResolution?: 'legacy' | 'corrected' 就是用來解決這類問題的 (問題不只一種)
幸好我這次遇到的可以用這個 fix. 希望不要再有問題了。
Angular 的 Router 是真的很強, 但是也很多坑 ! 一堆問題沒人理.. 唉..
更新 : 2018-7-25
Base href 的奇妙
默認情況下, ng 會為每個項目加上一個 base href="/"
<base href="/">
依據規范, base href 的結尾必須是 slash "/" 比如 "/en/", "/cn/"
看看下面的情況
可以獲取圖 <base href="/"> <img src="/images/stickman.gif" width="24" height="39"> 可以獲取圖 <base href="/"> <img src="images/stickman.gif" width="24" height="39"> 不可以獲取圖 <base href="/images/"> <img src="/stickman.gif" width="24" height="39"> 可以獲取圖 <base href="/images/"> <img src="stickman.gif" width="24" height="39">
是不是有點奇葩... 反正 best pratice 就是一定要寫 base href, 如果沒有特別的就放 "/"
所有路徑都不要使用 slash "/" 作為開始, 這樣就安全了.
要在 ng 里獲取 base href 的值可以注入服務 PlatformLocation.getBaseHrefFromDOM()
更新 : 2017-12-19
今天遇到一個 關於 router empty path + relative 的 bug
https://github.com/angular/angular/issues/18059
https://github.com/angular/angular/issues/13011
https://github.com/angular/angular/issues/17957
也可能這個不是 bug 是 ng 的設計思路.
總只能結果就是如果你想在 component 內使用 relative routerLink 比如 "../" or "./" or "child-path"
那么請你確保你這個 component 所在的 router 一定要有 parent path. 如果所有 parent path 都是 empty string 那么你就 gg.com 了.
1 imports: [RouterModule.forRoot([ 2 { path: '', pathMatch: 'full', redirectTo: '/products' }, 3 { 4 path: '', 5 children: [ 6 { 7 path: '', 8 component: HomeComponent, 9 children: [{ 10 path: '', 11 component: CategoryComponent, // <a routerLink="products" >category go</a> 《--result is products/(products) .... 12 children: [ 13 { 14 path: 'products', 15 component: ProductComponent 16 } 17 ] 18 }] 19 } 20 ] 21 } 22 ])],
下面這個就 ok
imports: [RouterModule.forRoot([ { path: '', pathMatch: 'full', redirectTo: 'dada/products' }, { path: '', children: [ { path: 'dada', // 要有 parent path component: HomeComponent, children: [{ path: '', component: CategoryComponent, // <a routerLink="products" >category go</a> OK ! children: [ { path: 'products', component: ProductComponent } ] }] } ] } ])],
更新 : 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
參考 :
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 哦