路由知識總結
可以把SPA(single page application)理解爲是一個視圖狀態的集合。Angular架構下的各個視圖會因爲操作的不同顯示的也會各有千秋,這些功勞全都得歸功於路由。
基礎知識
路由相關的對象總結:
-
Routes:路由配置,表示在哪個URL中會顯示哪個組件,還有就是在哪個RouterOutlet(像是一個插排一樣)中顯示組件。
/** * Tips: path 不能使用斜槓進行開頭,因爲可以讓Angular自動使用絕對路徑和相對路徑。 * 配置好路徑以及路徑對應的需要顯示的component。 */ const routes: Routes = [ {path: '', component: HomeComponent}, {path: 'products', component: ProductsComponent} ];
-
RouterOutlet:在HTML標記路由內的佔位符指令。
<router-outlet></router-outlet>
-
Router:在運行時指定路由的行爲,通過navigate()以及navigateByURL()指定路由到哪個路由中去。
//html模版上寫入一個點擊事件,通過事件,觸發clickProductButton事件。通過router實現路由。 constructor( private router: Router ) {} public clickProductButton() { this.router.navigate(['/products']); }
-
RouterLink:在HTML中聲明路由導航用的指令。與Router相類似,只不過Router是在controller中使用的,而RouterLink在HTML中使用的。
<!--必須加入斜槓,因爲這樣才能區分是跟路由,還是子路由--> <!--爲什麼 routerLink的值是一個數組呢,因爲可以通過路由傳入一些參數--> <a [routerLink]="['/']">主頁</a> <a [routerLink]="['/products']">商品詳情</a>
- ActivatedRoute:當前激活路由的相關信息,可以被這個類記錄,並且被我們使用。
如何在路由中傳遞數據
-
在查詢參數中傳遞數據
多添加一個[queryParams]的屬性綁定形如:
<a [routerLink]="['/products']" [queryParams]= "{id:1}">商品詳情</a>
獲取:通過ActivatedRoute.queryParams[參數的key]
-
在路由路徑中傳遞數據
- 修改Routes中的path屬性,形如:path:'product/:type'
- routerLink中多添加一個參數,形如:[routerLink]="['/products','book']" ,這裏的book就是給我們剛剛定義type的值。
獲取:通過 ActivatedRoute.params[參數的key]
-
在路由配置中傳遞數據
通過在Routes中定義data參數 形如:
{path: '', component: HomeComponent, data: [{key: value}]}
然後通過ActivatedRoute.data[0] [key] 來獲取
Tips:參數快照與參數訂閱
首先上代碼:
//參數訂閱
this.activatedRoute.params.subscribe((params: Params) => {
this.productType = params['type'];
});
//參數快照
this.productType = this.activatedRoute.snapshot.params['type'];
他倆的區別就在於我們現在有兩個不同的按鈕,跳轉到的URL分別爲 [routerLink]="['/products','book']",和[routerLink]="['/products','watch']",可以看出它們只有type的參數類型不同。
如果使用了快照,點擊了第一個按鈕,在點擊第二個,那麼獲取到的參數不會發生變化,這個時候我們就應該使用參數訂閱(觀察者模式的思想,感興趣的可以查詢RXJS,進行詳細瞭解)。
重定向路由
在Routes中添加 對應參數:
{path: '', redirectTo: '/home', pathMatch: 'full'}
子路由
在正常的情況下,組件與組件之間一定是會有嵌套關係的,這種嵌套關係就會導致我們的路由插座(<router-outlet>)同樣也是嵌套的。子路由就是爲了解決路由插座父子嵌套關係的
使用子路由的步驟:
-
修改在Routes中,product的路由信息,主要就是添加了一個children屬性:
{path: 'products/:type', component: ProductsComponent, children: [ {path: '', component: ProductDescComponent}, {path: 'seller/:id', component: SellerComponent} ]}
在需要子路由的html中,插上<router-outlet></router-outlet> 作爲插座
-
然後在需要跳轉的地方編寫如下代碼
<a [routerLink] = "['./']">跳轉到商品詳情</a> <a [routerLink] = "['./seller', 99]">跳轉到售貨員信息</a>
輔助路由
剛剛的子路由如果說是父子關係的話,那麼輔助路由就是"兄弟關係了"。
這種場景出現在我們在一個界面中,兩個component分別被不同的路由機制管理着,如果只使用原來的<router-outlet>插槽,沒有辦法指定用的到底是哪一種路由策略,所以輔助路由就這麼誕生了。
使用輔助路由的步驟:
通過name 指定具體的路由插座名稱
<router-outlet></router-outlet>
<router-outlet name="aux"></router-outlet>
指定當前這個aux路由可以展示哪些component。
{path: /xxx, component: XxxComponent, outlet: aux}
{path: /yyy, component: YyyComponent, outlet: aux}
在進行導航的地方指定我們需要的那個路由
<a [routerLink] ="[{outlets: {aux: 'xxx'}}]"></a>
<a [routerLink] ="[{outlets: {aux: 'xxx'}}]"></a>
路由守衛
頁面從一種頁面狀態跳轉到另一種頁面狀態,有的時候需要一些條件,檢查這些條件就是路由守衛的職責。
一共可以分爲三種:
-
CanActivate: 處理導航到某路由的情況
大概的使用步驟:
首先我們先要寫一個守衛的類:
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; /** * 這個路由守衛用於實現進入某以頁面需要滿足某些需求的情況。 */ export class LoginGuard implements CanActivate { private flag = true; canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { //這裏給了一個隨機數,如果數字大於0.5則可以進行登陸,否則會被攔截 this.flag = Math.random() > 0.5; if ( this.flag ) { console.log('可以登陸'); } console.log(this.flag); return this.flag; } }
然後將守衛的類添加到Routes中需要守衛的規則中:
{path: 'products/:type', component: ProductsComponent, canActivate: [LoginGuard], children: [ {path: '', component: ProductDescComponent}, {path: 'seller/:id', component: SellerComponent} ]}
最後在app.module.ts中添加自己需要依賴注入的守衛類即可:
providers: [LoginGuard]
-
CanDeactive: 處理從當前路由離開的情況
大概的使用步驟:
首先我們先要寫一個守衛的類:
import { CanDeactivate, ActivatedRouteSnapshot } from "@angular/router"; import { ProductsComponent } from "../products/products.component"; export class NotSaveGuard implements CanDeactivate<ProductsComponent> { private flag = true; canDeactivate(component: ProductsComponent, _currentRoute: ActivatedRouteSnapshot) { //這裏暫時給出一個提示框 return window.confirm("還沒有保存確定離開嗎?"); } }
然後將守衛的類添加到Routes中需要守衛的規則中:
{path: 'products/:type', component: ProductsComponent, canDeActivate: [NotSaveGuard], children: [ {path: '', component: ProductDescComponent}, {path: 'seller/:id', component: SellerComponent} ]}
最後在app.module.ts中添加自己需要依賴注入的守衛類即可:
providers: [NotSaveGuard]
-
Resolve:在路由激活之前獲取數據
在進入路由之前檢測數據是不是已經存在,以爲網絡請求具有延遲,如果出現了,已經路由到下個界面,但是信息還沒有存在的情況,我們就會讓界面路由到錯誤界面或者別的什麼界面。
大概的使用步驟:
1.首先我們定義一個Resolve守衛的類:
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from "@angular/router"; import { ProductsComponent, Product } from "../products/products.component"; import { Injectable } from "@angular/core"; @Injectable() export class ProductGuard implements Resolve<Product> { constructor(private router: Router) { } resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { if (route.params['type'] === 'book') { return new Product(1, 'iphone X'); } else { this.router.navigate(['/home']); return undefined; } } }
2.然後將resolve屬性添加到守衛的規則中
{path: 'products/:type', component: ProductsComponent, resolve: {product: ProductGuard}, children: [ {path: '', component: ProductDescComponent}, {path: 'seller/:id', component: SellerComponent} ]}
3.依賴注入 ProductGuard
providers: [ProductGuard]
4.resolve 其實相當於對返回值的一種增強,接受返回值的地方我們應該這麼寫
this.activatedRoute.data.subscribe((data: {product: Product}) => { //注意:這裏之所以可以使用data.product,是因爲我們在Routes路由中配置的 resolve: {product: ProductGuard}所致。這裏的product就是返回值的名字,如果變化了,兩個名字都要一起變化。 this.productId = data.product.id; this.productName = data.product.name; });
最後附加上本文提及到的代碼,我已經放在github上,可供參考