本主題講述當(dāng)把 Angular 路由器添加到應(yīng)用中時,如何實現(xiàn)多種常見路由任務(wù)。
下面的命令會用 Angular CLI 來生成一個帶有應(yīng)用路由模塊(?AppRoutingModule
?)的基本 Angular 應(yīng)用,它是一個 NgModule,可用來配置路由。下面的例子中應(yīng)用的名字是 ?routing-app
?。
ng new routing-app --routing --defaults
為了使用 Angular 的路由器,應(yīng)用至少要有兩個組件才能從一個導(dǎo)航到另一個。要使用 CLI 創(chuàng)建組件,請在命令行輸入以下內(nèi)容,其中 ?first
?是組件的名稱:
ng generate component first
為第二個組件重復(fù)這個步驟,但給它一個不同的名字。這里的新名字是 ?second
?。
ng generate component second
CLI 會自動添加 ?Component
?后綴,所以如果在編寫 ?first-component
?,那么其組件名就是 ?FirstComponentComponent
?。
<base href>
本指南適用于 CLI 生成的 Angular 應(yīng)用。如果你是手動工作的,請確保你的 index.html 文件的 ?<head>
? 中有 ?<base href="/">
? 語句。這里假定 ?app
?文件夾是應(yīng)用的根目錄,并使用 ?"/"
? 作為基礎(chǔ)路徑。
要使用這些新組件,請把它們導(dǎo)入到該文件頂部的 ?AppRoutingModule
?中,具體如下:
import { FirstComponent } from './first/first.component';
import { SecondComponent } from './second/second.component';
創(chuàng)建路由有三個基本的構(gòu)建塊。
把 ?AppRoutingModule
?導(dǎo)入 ?AppModule
?并把它添加到 ?imports
?數(shù)組中。
Angular CLI 會為你執(zhí)行這一步驟。但是,如果要手動創(chuàng)建應(yīng)用或使用現(xiàn)存的非 CLI 應(yīng)用,請驗證導(dǎo)入和配置是否正確。下面是使用 ?--routing
? 標志生成的默認 ?AppModule
?。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module'; // CLI imports AppRoutingModule
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule // CLI adds AppRoutingModule to the AppModule's imports array
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
RouterModule
?和 ?Routes
?導(dǎo)入到你的路由模塊中。Angular CLI 會自動執(zhí)行這一步驟。CLI 還為你的路由設(shè)置了 ?Routes
?數(shù)組,并為 ?@NgModule()
? 配置了 ?imports
?和 ?exports
?數(shù)組。
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; // CLI imports router
const routes: Routes = []; // sets up routes constant where you define your routes
// configures NgModule imports and exports
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Routes
?數(shù)組中定義你的路由。這個數(shù)組中的每個路由都是一個包含兩個屬性的 JavaScript 對象。第一個屬性 ?path
?定義了該路由的 URL 路徑。第二個屬性 ?component
?定義了要讓 Angular 用作相應(yīng)路徑的組件。
const routes: Routes = [
{ path: 'first-component', component: FirstComponent },
{ path: 'second-component', component: SecondComponent },
];
現(xiàn)在你已經(jīng)定義了路由,可以把它們添加到應(yīng)用中了。首先,添加到這兩個組件的鏈接。把要添加路由的鏈接賦值給 ?routerLink
?屬性。將屬性的值設(shè)置為該組件,以便在用戶點擊各個鏈接時顯示這個值。接下來,修改組件模板以包含 ?<router-outlet>
? 標簽。該元素會通知 Angular,你可以用所選路由的組件更新應(yīng)用的視圖。
<h1>Angular Router App</h1>
<!-- This nav gives you links to click, which tells the router which route to use (defined in the routes constant in AppRoutingModule) -->
<nav>
<ul>
<li><a routerLink="/first-component" routerLinkActive="active">First Component</a></li>
<li><a routerLink="/second-component" routerLinkActive="active">Second Component</a></li>
</ul>
</nav>
<!-- The routed views render in the <router-outlet>-->
<router-outlet></router-outlet>
路由的順序很重要,因為 ?Router
?在匹配路由時使用“先到先得”策略,所以應(yīng)該在不那么具體的路由前面放置更具體的路由。首先列出靜態(tài)路徑的路由,然后是一個與默認路由匹配的空路徑路由。通配符路由是最后一個,因為它匹配每一個 URL,只有當(dāng)其它路由都沒有匹配時,?Router
?才會選擇它。
通常,當(dāng)用戶導(dǎo)航你的應(yīng)用時,你會希望把信息從一個組件傳遞到另一個組件。例如,考慮一個顯示雜貨商品購物清單的應(yīng)用。列表中的每一項都有一個唯一的 ?id
?。要想編輯某個項目,用戶需要單擊“編輯”按鈕,打開一個 ?EditGroceryItem
?組件。你希望該組件得到該商品的 ?id
?,以便它能向用戶顯示正確的信息。
可以用一個路由把這種類型的信息傳給你的應(yīng)用組件。要做到這一點,你可以使用 ?ActivatedRoute
?接口。
要從路由中獲取信息:
ActivatedRoute
?和 ?ParamMap
?導(dǎo)入你的組件。import { Router, ActivatedRoute, ParamMap } from '@angular/router';
這些 import 語句添加了組件所需的幾個重要元素。
ActivatedRoute
?的一個實例添加到你的應(yīng)用的構(gòu)造函數(shù)中來注入它:constructor(
private route: ActivatedRoute,
) {}
ngOnInit()
? 方法來訪問這個 ?ActivatedRoute
?并跟蹤 ?name
?參數(shù):ngOnInit() {
this.route.queryParams.subscribe(params => {
this.name = params['name'];
});
}
注意:前面的例子使用了一個變量 ?name
?,并根據(jù) ?name
?參數(shù)給它賦值。
當(dāng)用戶試圖導(dǎo)航到那些不存在的應(yīng)用部件時,在正常的應(yīng)用中應(yīng)該能得到很好的處理。要在應(yīng)用中添加此功能,需要設(shè)置通配符路由。當(dāng)所請求的 URL 與任何路由器路徑都不匹配時,Angular 路由器就會選擇這個路由。
要設(shè)置通配符路由,請在 ?routes
?定義中添加以下代碼。
{ path: '**', component: }
這兩個星號 ?**
? 告訴 Angular,這個 ?routes
?定義是通配符路由。對于 component 屬性,你可以使用應(yīng)用中的任何組件。常見的選擇包括應(yīng)用專屬的 ?PageNotFoundComponent
?,你可以定義它來向用戶展示 404 頁面,或者跳轉(zhuǎn)到應(yīng)用的主組件。通配符路由是最后一個路由,因為它匹配所有的 URL。
要顯示 404 頁面,請設(shè)置一個通配符路由,并將 ?component
?屬性設(shè)置為你要用于 404 頁面的組件,如下所示:
const routes: Routes = [
{ path: 'first-component', component: FirstComponent },
{ path: 'second-component', component: SecondComponent },
{ path: '**', component: PageNotFoundComponent }, // Wildcard route for a 404 page
];
?path
?為 ?**
? 的最后一條路由是通配符路由。如果請求的 URL 與前面列出的路徑不匹配,路由器會選擇這個路由,并把該用戶送到 ?PageNotFoundComponent
?。
要設(shè)置重定向,請使用重定向源的 ?path
?、要重定向目標的 ?component
?和一個 ?pathMatch
?值來配置路由,以告訴路由器該如何匹配 URL。
const routes: Routes = [
{ path: 'first-component', component: FirstComponent },
{ path: 'second-component', component: SecondComponent },
{ path: '', redirectTo: '/first-component', pathMatch: 'full' }, // redirect to `first-component`
{ path: '**', component: PageNotFoundComponent }, // Wildcard route for a 404 page
];
在這個例子中,第三個路由是重定向路由,所以路由器會默認跳到 ?first-component
? 路由。注意,這個重定向路由位于通配符路由之前。這里的 ?path: ''
? 表示使用初始的相對 URL( ?''
? )。
隨著你的應(yīng)用變得越來越復(fù)雜,你可能要創(chuàng)建一些根組件之外的相對路由。這些嵌套路由類型稱為子路由。這意味著你要為你的應(yīng)用添加第二 ?<router-outlet>
?,因為它是 ?AppComponent
?之外的另一個 ?<router-outlet>
?。
在這個例子中,還有兩個子組件,?child-a
? 和 ?child-b
?。這里的 ?FirstComponent
?有它自己的 ?<nav>
? 和 ?AppComponent
?之外的第二 ?<router-outlet>
?。
<h2>First Component</h2>
<nav>
<ul>
<li><a routerLink="child-a">Child A</a></li>
<li><a routerLink="child-b">Child B</a></li>
</ul>
</nav>
<router-outlet></router-outlet>
子路由和其它路由一樣,同時需要 ?path
?和 ?component
?。唯一的區(qū)別是你要把子路由放在父路由的 ?children
?數(shù)組中。
const routes: Routes = [
{
path: 'first-component',
component: FirstComponent, // this is the component with the <router-outlet> in the template
children: [
{
path: 'child-a', // child route path
component: ChildAComponent, // child route component that the router renders
},
{
path: 'child-b',
component: ChildBComponent, // another child route component that the router renders
},
],
},
];
相對路徑允許你定義相對于當(dāng)前 URL 段的路徑。下面的例子展示了到另一個組件 ?second-component
? 的相對路由。?FirstComponent
?和 ?SecondComponent
?在樹中處于同一級別,但是,指向 ?SecondComponent
?的鏈接位于 ?FirstComponent
?中,這意味著路由器必須先上升一個級別,然后進入二級目錄才能找到 ?SecondComponent
???梢杂?nbsp;?../
? 符號來上升一個級別,而不用寫出到 ?SecondComponent
?的完整路徑。
<h2>First Component</h2>
<nav>
<ul>
<li><a routerLink="../second-component">Relative Route to second component</a></li>
</ul>
</nav>
<router-outlet></router-outlet>
除了 ?../
?,還可以使用 ?./
? 或者不帶前導(dǎo)斜杠來指定當(dāng)前級別。
要指定相對路由,請使用 ?NavigationExtras
?中的 ?relativeTo
? 屬性。在組件類中,從 ?@angular/router
? 導(dǎo)入 ?NavigationExtras
?。
然后在導(dǎo)航方法中使用 ?relativeTo
? 參數(shù)。在鏈接參數(shù)數(shù)組(它包含 ?items
?)之后添加一個對象,把該對象的 ?relativeTo
?屬性設(shè)置為當(dāng)前的 ?ActivatedRoute
?,也就是 ?this.route
?。
goToItems() {
this.router.navigate(['items'], { relativeTo: this.route });
}
?goToItems()
? 方法會把目標 URI 解釋為相對于當(dāng)前路由的,并導(dǎo)航到 ?items
?路由。
有時,應(yīng)用中的某個特性需要訪問路由的部件,比如查詢參數(shù)或片段(fragment)。本教程的這個階段使用了一個“英雄之旅”中的列表視圖,你可以在其中點擊一個英雄來查看詳情。路由器使用 ?id
?來顯示正確的英雄的詳情。
首先,在要導(dǎo)航的組件中導(dǎo)入以下成員。
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
接下來,注入當(dāng)前路由(ActivatedRoute)服務(wù):
constructor(private route: ActivatedRoute) {}
配置這個類,讓你有一個可觀察對象 ?heroes$
?、一個用來保存英雄的 ?id
?號的 ?selectedId
?,以及 ?ngOnInit()
? 中的英雄們,添加下面的代碼來獲取所選英雄的 ?id
?。這個代碼片段假設(shè)你有一個英雄列表、一個英雄服務(wù)、一個能獲取你的英雄的函數(shù),以及用來渲染你的列表和細節(jié)的 HTML,就像在《英雄之旅》例子中一樣。
heroes$: Observable;
selectedId: number;
heroes = HEROES;
ngOnInit() {
this.heroes$ = this.route.paramMap.pipe(
switchMap(params => {
this.selectedId = Number(params.get('id'));
return this.service.getHeroes();
})
);
}
接下來,在要導(dǎo)航到的組件中,導(dǎo)入以下成員。
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { Observable } from 'rxjs';
在組件類的構(gòu)造函數(shù)中注入 ?ActivatedRoute
?和 ?Router
?,這樣在這個組件中就可以用它們了:
hero$: Observable;
constructor(
private route: ActivatedRoute,
private router: Router ) {}
ngOnInit() {
const heroId = this.route.snapshot.paramMap.get('id');
this.hero$ = this.service.getHero(heroId);
}
gotoItems(hero: Hero) {
const heroId = hero ? hero.id : null;
// Pass along the hero id if available
// so that the HeroList component can select that item.
this.router.navigate(['/heroes', { id: heroId }]);
}
你可以配置路由定義來實現(xiàn)惰性加載模塊,這意味著 Angular 只會在需要時才加載這些模塊,而不是在應(yīng)用啟動時就加載全部。 另外,你可以在后臺預(yù)加載一些應(yīng)用部件來改善用戶體驗。
使用路由守衛(wèi)來防止用戶未經(jīng)授權(quán)就導(dǎo)航到應(yīng)用的某些部分。Angular 中提供了以下路由守衛(wèi):
CanActivate
?CanActivateChild
?CanDeactivate
?Resolve
?CanLoad
?要想使用路由守衛(wèi),可以考慮使用無組件路由,因為這對于保護子路由很方便。
為你的守衛(wèi)創(chuàng)建一項服務(wù):
ng generate guard your-guard
請在守衛(wèi)類里實現(xiàn)你要用到的守衛(wèi)。下面的例子使用 ?CanActivate
?來保護該路由。
export class YourGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
// your logic goes here
}
}
在路由模塊中,在 ?routes
?配置中使用相應(yīng)的屬性。這里的 ?canActivate
?會告訴路由器它要協(xié)調(diào)到這個特定路由的導(dǎo)航。
{
path: '/your-path',
component: YourComponent,
canActivate: [YourGuard],
}
鏈接參數(shù)數(shù)組保存路由導(dǎo)航時所需的成分:
可以把 ?RouterLink
?指令綁定到一個數(shù)組,就像這樣:
<a [routerLink]="['/heroes']">Heroes</a>
在指定路由參數(shù)時,使用如下的兩元素數(shù)組:
<a [routerLink]="['/hero', hero.id]">
<span class="badge">{{ hero.id }}</span>{{ hero.name }}
</a>
可以在對象中提供可選的路由參數(shù),比如 ?{ foo: 'foo' }
? :
<a [routerLink]="['/crisis-center', { foo: 'foo' }]">Crisis Center</a>
這三個例子涵蓋了你在單級路由的應(yīng)用中所需的一切。不過,在你添加一個像危機中心一樣的子路由時,你可以創(chuàng)建新鏈接數(shù)組。
下面這個最小化 ?RouterLink
?例子是基于危機中心指定的默認子路由構(gòu)建的。
<a [routerLink]="['/crisis-center']">Crisis Center</a>
請注意以下事項:
/crisis-center
?)。CrisisListComponent
?,它的路由路徑是'/',但你不用顯式的添加它。考慮以下路由器鏈接,它將從應(yīng)用的根目錄導(dǎo)航到巨龍危機(Dragon Crisis):
<a [routerLink]="['/crisis-center', 1]">Dragon Crisis</a>
/crisis-center
?)。id
?路由參數(shù)。id
?添加為該數(shù)組中的第二個條目(1)。/crisis-center/1
?。你也可以把危機中心的路由單獨重新定義為 ?AppComponent
?的模板:
template: `
<h1 class="title">Angular Router</h1>
<nav>
<a [routerLink]="['/crisis-center']">Crisis Center</a>
<a [routerLink]="['/crisis-center/1', { foo: 'foo' }]">Dragon Crisis</a>
<a [routerLink]="['/crisis-center/2']">Shark Crisis</a>
</nav>
<router-outlet></router-outlet>
`
總之,你可以用一級、兩級或多級路由來寫應(yīng)用程序。 鏈接參數(shù)數(shù)組提供了用來表示任意深度路由的鏈接參數(shù)數(shù)組以及任意合法的路由參數(shù)序列、必須的路由器參數(shù)以及可選的路由參數(shù)對象。
當(dāng)路由器導(dǎo)航到一個新的組件視圖時,它會用該視圖的 URL 來更新瀏覽器的當(dāng)前地址以及歷史。
現(xiàn)代 HTML 5 瀏覽器支持history.pushState API, 這是一項可以改變?yōu)g覽器的當(dāng)前地址和歷史,卻又不會觸發(fā)服務(wù)端頁面請求的技術(shù)。 路由器可以合成出一個“自然的”URL,它看起來和那些需要進行頁面加載的 URL 沒什么區(qū)別。
下面是危機中心的 URL 在“HTML 5 pushState”風(fēng)格下的樣子:
localhost:3002/crisis-center/
老舊的瀏覽器在當(dāng)前地址的 URL 變化時總會往服務(wù)器發(fā)送頁面請求……唯一的例外規(guī)則是:當(dāng)這些變化位于“#”(被稱為“hash”)后面時不會發(fā)送。通過把應(yīng)用內(nèi)的路由 URL 拼接在 ?#
? 之后,路由器可以獲得這條“例外規(guī)則”帶來的優(yōu)點。下面是到危機中心路由的“hash URL”:
localhost:3002/src/#/crisis-center/
路由器通過兩種 ?LocationStrategy
?提供者來支持所有這些風(fēng)格:
PathLocationStrategy
?- 默認的策略,支持“HTML 5 pushState”風(fēng)格。HashLocationStrategy
?- 支持“hash URL”風(fēng)格。?RouterModule.forRoot()
? 函數(shù)把 ?LocationStrategy
?設(shè)置成了 ?PathLocationStrategy
?,使其成為了默認策略。 你還可以在啟動過程中改寫(override)它,來切換到 ?HashLocationStrategy
?風(fēng)格。
你必須在開發(fā)項目的早期就選擇一種路由策略,因為一旦該應(yīng)用進入了生產(chǎn)階段,你網(wǎng)站的訪問者就會使用并依賴應(yīng)用的這些 URL 引用。
幾乎所有的 Angular 項目都會使用默認的 HTML 5 風(fēng)格。它生成的 URL 更易于被用戶理解,它也為將來做服務(wù)端渲染預(yù)留了空間。
在服務(wù)器端渲染指定的頁面,是一項可以在該應(yīng)用首次加載時大幅提升響應(yīng)速度的技術(shù)。那些原本需要十秒甚至更長時間加載的應(yīng)用,可以預(yù)先在服務(wù)端渲染好,并在少于一秒的時間內(nèi)完整渲染在用戶的設(shè)備上。
只有當(dāng)應(yīng)用的 URL 看起來像是標準的 Web URL,中間沒有 hash(#)時,這個選項才能生效。
路由器使用瀏覽器的 history.pushState API 進行導(dǎo)航。借助 ?pushState
?你自定義應(yīng)用中的 URL 路徑 ?localhost:4200/crisis-center
?,應(yīng)用內(nèi)的 URL 和服務(wù)器的 URL 沒有區(qū)別。
現(xiàn)代的 HTML5 瀏覽器都支持 ?pushState
?,這也就是為什么很多人把這種 URL 形式稱為 "HTML 5" 風(fēng)格的 URL。
你必須在應(yīng)用的 ?index.html
? 中添加一個 <base href> 元素才能讓 ?pushState
?路由正常工作。 瀏覽器要用 ?<base href>
? 的值為引用 CSS、腳本和圖片文件時使用的相對 URL 添加前綴。
請把 ?<base>
? 元素添加在 ?<head>
? 標簽的緊后面。如果應(yīng)用的根目錄是 ?app
?目錄,那么就可以像這個應(yīng)用程序一樣,設(shè)置 ?index.html
? 中的 ?href
?值。代碼如下。
<base href="/">
后面的指南中會引用 URL 的不同部分。下圖是這些部分所指內(nèi)容的梗概:
foo://example.com:8042/over/there?name=ferret#nose
\_/ \______________/\_________/ \_________/ \__/
| | | | |
scheme authority path query fragment
由于路由器默認使用 “HTML 5 pushState” 風(fēng)格,所以你必須用一個 ?<base href>
? 來配置該策略(Strategy)。
配置該策略的首選方式是往 ?index.html
? 的 ?<head>
? 中添加一個<base href> element標簽。
<base href="/">
如果沒有該標記,瀏覽器就可能無法在“深度鏈接”進入應(yīng)用時加載資源(圖片,CSS,腳本)。
有些開發(fā)人員可能無法添加 ?<base>
? 元素,這可能是因為它們沒有訪問 ?<head>
? 或 ?index.html
? 的權(quán)限。
它們?nèi)匀豢梢允褂?nbsp;HTML 5 格式的 URL,但要采取如下步驟進行補救:
<base href>
? 的 ?path
?應(yīng)該用 "/" 結(jié)尾,瀏覽器會忽略 ?path
?中最右邊的 "/" 后面的字符。<base href>
? 包含 ?query
?部分,則只有頁內(nèi)鏈接的 ?path
?部分為空并且沒有 ?query
?時,才會使用這里的 ?query
?。 這意味著 ?<base href>
? 中的 ?query
?部分只有在使用 ?HashLocationStrategy
?策略時才有用。<base href>
? 不會使用。在這種方式下,?APP_BASE_HREF
?的優(yōu)先度將會導(dǎo)致所有由 Angular 創(chuàng)建的鏈接忽略 ?<base href>
?。<base href>
? 中的片段(#后面的部分)永遠不會被使用。對所有 Web 資源使用絕對地址:CSS、圖片、腳本、模板 HTML。
可以在根模塊的 ?RouterModule.forRoot()
? 的第二個參數(shù)中傳入一個帶有 ?useHash: true
? 的對象,以回到基于 ?HashLocationStrategy
?的傳統(tǒng)方式。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { Routes, RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
const routes: Routes = [
];
@NgModule({
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot(routes, { useHash: true }) // .../#/crisis-center/
],
declarations: [
AppComponent,
PageNotFoundComponent
],
providers: [
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
更多建議: