摘要:在本教程中,Ahmed Bouchefra 介紹了angular路由器(router),以及如何使用它創(chuàng)建客戶(hù)端應(yīng)用和帶路由導(dǎo)航的單頁(yè)面應(yīng)用。
如果你對(duì)angular7還不熟悉的話(huà),我將帶你近距離了解這個(gè)令人印象深刻的前端框架所提供的一切。我會(huì)向你演示一個(gè)angular demo,通過(guò)這個(gè)demo你將了解與路由相關(guān)的一些概念,比如:
- 路由出口(The router outlet)
- 路由(Routes )和 路徑(paths)
- 導(dǎo)航(Navigation)
我也將向你演示如何使用Angular CLI v7生成一個(gè)demo應(yīng)用,在這個(gè)應(yīng)用中,我們將使用angular路由器實(shí)現(xiàn)路由和導(dǎo)航。但在介紹angluar路由之前,讓我們一起重溫一下angular以及它在最新版本中增加的一些新特性。
Anguar7 的介紹
angular是最受歡迎的構(gòu)建移動(dòng)端和pc端應(yīng)用的前端框架之一,它遵循基于組件的體系結(jié)構(gòu),其中每個(gè)組件都是一段獨(dú)立的、可重用的代碼,控制著應(yīng)用程序UI的一部分。
angular中的每一個(gè)組件都是一個(gè)用@Component裝飾器裝飾的TypeScritp類(lèi)。它有一個(gè)附加模板和用于形成組件視圖的css樣式表。
angular7是angular最近剛發(fā)布的最新版本,它給angular帶來(lái)了很多新特性,尤其是在CLI和性能表現(xiàn)方面有了很大的提升,比如:
·CLI提示: 像 ng add 和ng new 這樣的普通命令現(xiàn)在可以提示用戶(hù)選擇要向項(xiàng)目中添加的命令,比如路由和樣式格式
- Angular Material CDK添加虛擬滾動(dòng)功能
- Angular Material CDK添加對(duì)拖放功能的支持
- 新項(xiàng)目會(huì)默認(rèn)使用CLI中的Budgets Bundle ,這將在app大小超過(guò)限定尺寸時(shí)向開(kāi)發(fā)者發(fā)出警告。默認(rèn)情況下,當(dāng)初始 bundle 超過(guò)2MB,新應(yīng)用程序?qū)l(fā)出警告,超過(guò)5MB時(shí)將會(huì)報(bào)錯(cuò)。你也可以在angular.json文件中修改這些限制值。
Angular Router 介紹
Angular Router是由angular 核心團(tuán)隊(duì)構(gòu)建和維護(hù)的一款強(qiáng)大的JavaScript路由器,可以從@angular/router包中安裝使用。它給開(kāi)發(fā)者提供了一個(gè)擁有眾功能的完整的路由庫(kù),這些功能包括 多路由出口,不同的路由匹配策略,易獲取的路由參數(shù),保護(hù)組件免受未授權(quán)訪問(wèn)的路由守衛(wèi)。
Angular Router是angular平臺(tái)的核心部分,它使得開(kāi)發(fā)者能夠使用多個(gè)視圖構(gòu)建單頁(yè)面應(yīng)用,并允許在這些視圖之間導(dǎo)航。
現(xiàn)在,讓我們來(lái)更詳細(xì)的了解路由器的基本概念。
路由出口(the router-outlet)
router-outlet 是路由庫(kù)中提供的一個(gè)指令,路由器根據(jù)當(dāng)前瀏覽器的URL插入相匹配的組件。你可以在你的應(yīng)用中加入多個(gè)outlet來(lái)實(shí)現(xiàn)高級(jí)的路由場(chǎng)景
<router-outlet></router-outlet>
路由器將把它匹配的任何組件呈現(xiàn)為路由出口的同級(jí)組件
路由和路徑(routes and paths)
Routes 是一系列定義(對(duì)象集)(譯者注:也就是路由配置對(duì)象的集合),每個(gè)對(duì)象包含至少一個(gè)path屬性和一個(gè)component屬性(或者一個(gè)redirectTo屬性)。path指URL中確定應(yīng)該顯示的唯一視圖的部分,component指需要與path相關(guān)聯(lián)的angular組件。根據(jù)我們提供的一個(gè)路由定義(通過(guò)靜態(tài)方法RouterModule.forRoot(routes)),路由器能夠?qū)⒂脩?hù)導(dǎo)航到特定的視圖
每個(gè)路由將一個(gè)URL路徑映射到唯一對(duì)應(yīng)的組件
path的值可以為空(''),這表示應(yīng)用的一個(gè)默認(rèn)路徑,空路徑通常是應(yīng)用的開(kāi)始。
path的值可以使用通配符字符串(**)。如果請(qǐng)求的URL無(wú)法匹配路由數(shù)組中任何定義的路由,路由器將會(huì)選擇通配符路由。通配符路由可以用來(lái)展示"Not Found"視圖,或者在沒(méi)有路由匹配的情況下重定向到特定的視圖
下面是一個(gè)路由的例子:
{ path: 'contacts', component: ContactListComponent}
如果將這個(gè)路由添加到路由器配置中,當(dāng)web應(yīng)用程序的瀏覽器URL變?yōu)?/contacts
時(shí),路由器將呈現(xiàn)ContactListComponent
組件。
路由匹配策略
angular路由器提供了多種不同的路由匹配策略。默認(rèn)的策略是檢查當(dāng)前瀏覽器URL是否以定義的path值為前綴。
例如我們的上一個(gè)路由:
{ path: 'contacts', component: ContactListComponent}
也可以寫(xiě)成下面的形式:
{ path: 'contacts',pathMatch: 'prefix', component: ContactListComponent}
pathMatch
屬性指定了路由匹配策略,默認(rèn)值是例子中的prefix
.
pathMatch
的另一個(gè)值是full
,當(dāng)指定一個(gè)路由的匹配策略為full
時(shí),路由器將檢查path的值是否與當(dāng)前瀏覽器URL完全匹配:
{ path: 'contacts',pathMatch: 'full', component: ContactListComponent}
路由參數(shù)
創(chuàng)建帶參數(shù)的路由是webapp的一個(gè)常見(jiàn)特性,Angular路由器允許你使用一下兩種不同的方式訪問(wèn)路由參數(shù):
- 使用ActivatedRoute服務(wù)
- 從angular4開(kāi)始可以使用ParamMap
你可以使用冒號(hào)語(yǔ)法創(chuàng)建一個(gè)路由參數(shù),下面是一個(gè)帶有id參數(shù)的路由例子:
{ path: 'contacts/:id', component: ContactDetailComponent}
路由守衛(wèi)
路由守衛(wèi)是angular的路由器的一個(gè)特性,它允許開(kāi)發(fā)者在一個(gè)路由被請(qǐng)求時(shí)運(yùn)行一些處理邏輯,并根據(jù)運(yùn)行結(jié)果決定允許或者拒絕用戶(hù)訪問(wèn)此路由。路由守衛(wèi)普遍被用在用戶(hù)訪問(wèn)一個(gè)頁(yè)面時(shí),檢查用戶(hù)是否已登錄并被授權(quán)。
你可以通過(guò)實(shí)現(xiàn)@angular/router
模塊中的CanActive
接口,重寫(xiě)canActivate()
方法來(lái)添加路由守衛(wèi)。canActivate()
方法用于存放是否允許訪問(wèn)路由的處理邏輯的。下面的守衛(wèi)將始終允許用戶(hù)訪問(wèn)一個(gè)路由:
class MyGuard implements CanActivate {
canActivate() {
return true;
}
}
然后,你可以使用canActive
屬性將路由守衛(wèi)添加到路由上,達(dá)到保護(hù)路由的目的:
{ path: 'contacts/:id', canActivate: [MyGuard], component: ContactDetailComponent}
導(dǎo)航指令
angular路由器提供了routerLink
指令來(lái)創(chuàng)建導(dǎo)航鏈接。這個(gè)指令采用路由中與組件相關(guān)聯(lián)的路徑進(jìn)行導(dǎo)航,形式如下:
<a [routerLink]="'/contacts'">Contacts</a>
多路由出口和輔助路由
angular路由器支持在同一應(yīng)用中使用多個(gè)路由出口(outlet)。
每個(gè)組件都有一個(gè)相關(guān)聯(lián)的主路由,也可以有多個(gè)輔助路由。輔助路由是開(kāi)發(fā)者能夠同時(shí)導(dǎo)航多個(gè)路由。
要?jiǎng)?chuàng)建輔助路由,你需要一個(gè)被命名的路由出口,與輔助路由相關(guān)聯(lián)的組件將會(huì)在這個(gè)命名的路由出口渲染。
<router-outlet></router-outlet>
<router-outlet name="outlet1"></router-outlet>
- 沒(méi)有
name
屬性的路由出口為主路由出口- 除了主路由出口外,所有的路由出口都應(yīng)該有一個(gè)
name
然后,你可以使用outlet
屬性指定你想渲染你的組件的那個(gè)路由出口:
{ path: "contacts", component: ContactListComponent, outlet: "outlet1" }
創(chuàng)建一個(gè) angular7 Demo
在這一部分,我們將看到一個(gè)如何安裝和使用angular路由器的實(shí)際例子。這里是我們將創(chuàng)建的demo演示以及github項(xiàng)目地址
安裝angular7
使用Angular CLI需要Nodejs 8.9+版本,npm 5.5.1+版本,開(kāi)發(fā)前確保你的系統(tǒng)中安裝了它們。然后運(yùn)行下面的命令行安裝最新版的Angular CLI
$ npm install -g @angular/cli
這條命令將會(huì)全局安裝Angular CLI
Note: 你或許想使用sudo
命令全裝安裝angluar-cli,這取決于你的npm配置
創(chuàng)建一個(gè)angular7 項(xiàng)目
簡(jiǎn)單的運(yùn)行下面這條命令,就可以創(chuàng)建一個(gè)新的項(xiàng)目:
$ ng new angular7-router-demo
CLI會(huì)詢(xún)問(wèn)你是否想添加路由(輸入N拒絕,因?yàn)槲覀儗?huì)在demo看到如何手動(dòng)添加路由),想使用哪種樣式表,選擇css ,然后按下Enter
。CLI將會(huì)創(chuàng)建包含項(xiàng)目必需文件的文件夾目錄,安裝項(xiàng)目所需的依賴(lài)。
創(chuàng)建虛擬后臺(tái)(fake back-end)服務(wù)
因?yàn)槲覀儧](méi)有真實(shí)的后臺(tái)可以交互,所以我們使用angular-in-memeory-web-api庫(kù)來(lái)創(chuàng)建一個(gè)虛擬后臺(tái)。這個(gè)庫(kù)是angluar用來(lái)演示和測(cè)試的內(nèi)存中的web api,它模擬了REST API的CRUD操作.
這個(gè)庫(kù)的工作方式是攔截HttpClient
向遠(yuǎn)程服務(wù)器發(fā)送的請(qǐng)求,并將請(qǐng)求重定向到我們創(chuàng)建的本地內(nèi)存數(shù)據(jù)存儲(chǔ)區(qū)。
為了創(chuàng)建一個(gè)虛擬后臺(tái),我們需要遵循以下步驟:
01 安裝
angular-in-memeory-web-api
模塊
02 創(chuàng)建一個(gè)返回假數(shù)據(jù)的服務(wù)
03 配置應(yīng)用程序使用虛擬后臺(tái)
在終端輸入下面的命令安裝angular-in-memory-web-api
模塊:
$ npm install --save angular-in-memory-web-api
然后,生成一個(gè)將要使用的后臺(tái)服務(wù):
ng g s backend
打開(kāi) src/app/backend.service.ts
文件,從angular-in-memory-web-api
模塊中引入 InMemoryDbService
:
import {InMemoryDbService} from 'angular-in-memory-web-api'
前面生成的BackendService
服務(wù)需要實(shí)現(xiàn)InMemoryDbService
接口并重寫(xiě)里面的createDb()
方法:
@Injectable({
providedIn: 'root'
})
export class BackendService implements InMemoryDbService{
constructor() { }
createDb(){
let contacts = [
{ id: 1, name: 'Contact 1', email: 'contact1@email.com' },
{ id: 2, name: 'Contact 2', email: 'contact2@email.com' },
{ id: 3, name: 'Contact 3', email: 'contact3@email.com' },
{ id: 4, name: 'Contact 4', email: 'contact4@email.com' }
];
return {contacts};
}
}
我們?cè)?code>BackendService中簡(jiǎn)單的創(chuàng)建了一個(gè)聯(lián)系人數(shù)組并返回了他們,每一個(gè)聯(lián)系人都有一個(gè)id
屬性。
最后,我們將InMemoryWebApiModule
模塊引入到app.module.ts
中,并提供我們創(chuàng)建的BackendService
:
import { InMemoryWebApiModule } from “angular-in-memory-web-api”;
import { BackendService } from “./backend.service”;
/* ... */
@NgModule({
declarations: [
/*...*/
],
imports: [
/*...*/
InMemoryWebApiModule.forRoot(BackendService)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
接下來(lái)創(chuàng)建一個(gè)ContactService
服務(wù),它封裝了處理聯(lián)系人的代碼:
$ ng g s contact
打開(kāi)src/app/contact.service.ts
文件,將它修改為下面的內(nèi)容:
import { HttpClient } from “@angular/common/http”;
@Injectable({
providedIn: 'root'
})
export class ContactService {
API_URL: string = "/api/";
constructor(private http: HttpClient) { }
getContacts(){
return this.http.get(this.API_URL + 'contacts')
}
getContact(contactId){
return this.http.get(`${this.API_URL + 'contacts'}/${contactId}`)
}
}
在ContactService
中,我們添加了兩個(gè)方法:
getContacts()
- 獲取全部聯(lián)系人getContact()
- 通過(guò)id
獲取某一聯(lián)系人
你可以將 API_URL
設(shè)置成任何URL值,因?yàn)槲覀儾粫?huì)使用一個(gè)真實(shí)后臺(tái)。所有的請(qǐng)求都將被攔截并發(fā)送到我們所創(chuàng)建的虛擬后臺(tái)中。
創(chuàng)建我們的 Angular組件
在我們學(xué)習(xí)如何使用不同的路由特性之前,讓我們先為我們的項(xiàng)目創(chuàng)建一些組件。
打開(kāi)終端運(yùn)行下面的這些命令:
$ ng g c contact-list
$ ng g c contact-detail
這兩條命令將會(huì)生成一個(gè)ContactListComponent
組件和一個(gè)ContactListComponent
組將。將它們添加到根模塊中(譯者注:此模塊位于 app.module.ts
文件中)
配置路由模塊
在大多數(shù)情況下,你會(huì)使用Angular CLI創(chuàng)建帶路由配置的項(xiàng)目,但在這里,我們將手動(dòng)添加路由,如此一來(lái)我們能夠更好的理解angular中的路由是如何工作的。
添加路由模塊
我們需要添加AppRoutingModule,它包含我們的應(yīng)用程序路由和一個(gè)路由器出口,Angular將根據(jù)瀏覽器的當(dāng)前URL插入當(dāng)前匹配的組件。
我們將會(huì)看到:
- 如何為路由創(chuàng)建一個(gè)angular模塊并引入它
- 如何為不同的組件配置路由
- 如何配置路由出口
首先,讓我們?cè)谝粋€(gè) app-routing.module.ts
中創(chuàng)建路由模塊,在src/app
路徑下使用如下命令創(chuàng)建app-routing.module.ts
文件:
$ cd angular7-router-demo/src/app
$ touch app-routing.module.ts
譯者注:
touch
可能是作者使用的linux創(chuàng)建文件的命令,讀者可以直接在src/app
目錄下使用angular中自帶的ng g m app-routing
命令創(chuàng)建此路由模塊文件,然后根據(jù)作者下面的步驟修改此文件即可
打開(kāi)app-routing.module.ts
文件,將它修改為如下內(nèi)容:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
上述代碼中,我們首先從@angular/core
包中引入了NgModule
,這是一個(gè)用來(lái)創(chuàng)建Angular模塊的TypesScript裝飾器。
我們還從@angular/core
包引入了RouterModule
類(lèi)和Routes
類(lèi)。
RouterModule
提供了像RouterModule.forRoot()
這樣的靜態(tài)方法,用于將配置對(duì)象傳遞個(gè)路由器。
接下來(lái),我們定義了一個(gè)類(lèi)型為Routes
的常量數(shù)組routes
用來(lái)存放每一個(gè)路由的信息。
最后,我們創(chuàng)建并導(dǎo)出了一個(gè)名為AppRoutingModule
的模塊(你可以起任何你喜歡的名字),這個(gè)模塊是一個(gè)由@NgModule
裝飾器裝飾的簡(jiǎn)單的TypeScript類(lèi),@NgModule 裝飾器接受一個(gè)元數(shù)據(jù)對(duì)象,該對(duì)象的屬性用來(lái)描述這個(gè)模塊。在這個(gè)對(duì)象的 imports
屬性中,我們調(diào)用了RouterModule.forRoot(routes)
方法,并將路由數(shù)組作為參數(shù)傳了進(jìn)去。在exports
數(shù)組中,我們添加了RouterModule
.
引入路由模塊
接下來(lái),我們將剛才創(chuàng)建的路由模塊引入位于src/app/app.module.ts
文件的根模塊中:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
我們從./app-routing.module
中引入了AppRoutingModule
,并將它添加到了根模塊的imports
數(shù)組中。
添加路由出口
最后,我們需要添加路由出口(router outlet)。打開(kāi)包含了主應(yīng)用模板的src/app/app.component.html
文件,添加<router-outlet>
:
<router-outlet></router-outlet>
Angular路由器將在這里渲染與當(dāng)前瀏覽器路徑相對(duì)應(yīng)的組件。
以上是在angular項(xiàng)目中手動(dòng)配置路由所需要遵循的所有步驟。
創(chuàng)建路由
現(xiàn)在,讓我們給我們的兩個(gè)組件添加路由。打開(kāi)src/app/app-routing.module.ts
,將下面的路由添加到routes
數(shù)組中:
const routes: Routes = [
{path: 'contacts' , component: ContactListComponent},
{path: 'contact/:id' , component: ContactDetailComponent}
];
要確保在路由模塊中引入了這兩個(gè)組件:
import { ContactListComponent } from './contact-list/contact-list.component';
import { ContactDetailComponent } from './contact-detail/contact-detail.component';
現(xiàn)在,我們就可以通過(guò)/contacts
和contact:id
訪問(wèn)這兩個(gè)組件了。
添加導(dǎo)航鏈接
接下來(lái),讓我們使用routerLink
指令將導(dǎo)航鏈接添加到我們的app模板上。打開(kāi)src/app/app.component.html
,在<router-outlet></router-outlet>
上面添加如下代碼:
<h2><a [routerLink] = "'/contacts'">Contacts</a></h2>
接下來(lái),我們需要在ContactListComponent
組件中展示聯(lián)系人列表。打開(kāi) src/app/contact-list.component.ts
,然后添加如下代碼:
import { Component, OnInit } from '@angular/core';
import { ContactService } from '../contact.service';
@Component({
selector: 'app-contact-list',
templateUrl: './contact-list.component.html',
styleUrls: ['./contact-list.component.css']
})
export class ContactListComponent implements OnInit {
contacts: any[] = [];
constructor(private contactService: ContactService) { }
ngOnInit() {
this.contactService.getContacts().subscribe((data : any[])=>{
console.log(data);
this.contacts = data;
})
}
}
我們創(chuàng)建了一個(gè)contacts
數(shù)組來(lái)存放聯(lián)系人數(shù)據(jù),接下來(lái),我們?cè)谠摻M件中注入ContactService
服務(wù),在ngOnInit
鉤子函數(shù)中調(diào)用服務(wù)實(shí)例的getContacts()
方法來(lái)獲取聯(lián)系人數(shù)據(jù)(前文BackendService
中創(chuàng)建的fake data)并將它賦值給contacts
數(shù)組。
下一步,打開(kāi)src/app/contact-list/contact-list.component.html
文件,添加如下代碼:
<table style="width:100%">
<tr>
<th>Name</th>
<th>Email</th>
<th>Actions</th>
</tr>
<tr *ngFor="let contact of contacts" >
<td>{{ contact.name }}</td>
<td>{{ contact.email }}</td>
<td>
<a [routerLink]="['/contact', contact.id]">Go to details</a>
</td>
</tr>
</table>
我們遍歷contacts
數(shù)組并展示每一個(gè)聯(lián)系人的姓名和email.同時(shí),我們也使用routerLink
指令創(chuàng)建了一個(gè)指向每一個(gè)聯(lián)系人詳情組件的鏈接(<a [routerLink]="['/contact', contact.id]">Go to details</a>
)。
下面是組件截屏:
當(dāng)我們點(diǎn)擊Go to detail
鏈接,路由器會(huì)將我們導(dǎo)航到ContactDetailsComponent
組件。導(dǎo)航路徑中有一個(gè)id
參數(shù),讓我們看看如何在詳情組件中訪問(wèn)這個(gè)參數(shù)。
打開(kāi)src/app/contact-detail/contact-detail.component.ts
文件,將它修改至如下形式:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ContactService } from '../contact.service';
@Component({
selector: 'app-contact-detail',
templateUrl: './contact-detail.component.html',
styleUrls: ['./contact-detail.component.css']
})
export class ContactDetailComponent implements OnInit {
contact: any;
constructor(private contactService: ContactService, private route: ActivatedRoute) { }
ngOnInit() {
this.route.paramMap.subscribe(params => {
console.log(params.get('id'))
this.contactService.getContact(params.get('id')).subscribe(c =>{
console.log(c);
this.contact = c;
})
});
}
}
我們?cè)诮M件中注入了ContactService
和ActivatedRoute
,在鉤子函數(shù)ngOnInit()
中檢索將從路由中傳遞過(guò)來(lái)的id
屬性,并使用它獲取我們分配給contact
對(duì)象的聯(lián)系人詳細(xì)信息。
打開(kāi)src/app/contact-detail/contact-detail.component.html
文件,添加:
<h1> Contact # {{contact.id}}</h1>
<p>
Name: {{contact.name}}
</p>
<p>
Email: {{contact.email}}
</p>
當(dāng)我們第一次從127.0.0.1:4200/
訪問(wèn)我們的應(yīng)用時(shí),路由出口不會(huì)渲染任何組件,因此讓我們向路由數(shù)組中添加如下路由來(lái)將空路徑重定向到contacts
:
{path: '', pathMatch: 'full', redirectTo: 'contacts'}
我們想匹配完全空的路徑,所以我們將路由匹配策略指定為full
.
總結(jié)
在本教程中,我們了解了如何使用Angular路由器向應(yīng)用程序中添加路由和導(dǎo)航。我們了解了很多概念,例如路由出口,路由和路徑,并且我們創(chuàng)建了一個(gè)demo來(lái)實(shí)際展示這些不同的概念。你可以在github上獲取這些代碼。
關(guān)于本文
作者:@Ahmed
原文:https://www.smashingmagazine.com/2018/11/a-complete-guide-to-routing-in-angular/