摘要:在本教程中,Ahmed Bouchefra 介绍了angular路由器(router),以及如何使用它创建客户端应用和带路由导航的单页面应用。
如果你对angular7还不熟悉的话,我将带你近距离了解这个令人印象深刻的前端框架所提供的一切。我会向你演示一个angular demo,通过这个demo你将了解与路由相关的一些概念,比如:
- 路由出口(The router outlet)
- 路由(Routes )和 路径(paths)
- 导航(Navigation)
我也将向你演示如何使用Angular CLI v7生成一个demo应用,在这个应用中,我们将使用angular路由器实现路由和导航。但在介绍angluar路由之前,让我们一起重温一下angular以及它在最新版本中增加的一些新特性。
Anguar7 的介绍
angular是最受欢迎的构建移动端和pc端应用的前端框架之一,它遵循基于组件的体系结构,其中每个组件都是一段独立的、可重用的代码,控制着应用程序UI的一部分。
angular中的每一个组件都是一个用@Component装饰器装饰的TypeScritp类。它有一个附加模板和用于形成组件视图的css样式表。
angular7是angular最近刚发布的最新版本,它给angular带来了很多新特性,尤其是在CLI和性能表现方面有了很大的提升,比如:
·CLI提示: 像 ng add 和ng new 这样的普通命令现在可以提示用户选择要向项目中添加的命令,比如路由和样式格式
- Angular Material CDK添加虚拟滚动功能
- Angular Material CDK添加对拖放功能的支持
- 新项目会默认使用CLI中的Budgets Bundle ,这将在app大小超过限定尺寸时向开发者发出警告。默认情况下,当初始 bundle 超过2MB,新应用程序将发出警告,超过5MB时将会报错。你也可以在angular.json文件中修改这些限制值。
Angular Router 介绍
Angular Router是由angular 核心团队构建和维护的一款强大的JavaScript路由器,可以从@angular/router包中安装使用。它给开发者提供了一个拥有众功能的完整的路由库,这些功能包括 多路由出口,不同的路由匹配策略,易获取的路由参数,保护组件免受未授权访问的路由守卫。
Angular Router是angular平台的核心部分,它使得开发者能够使用多个视图构建单页面应用,并允许在这些视图之间导航。
现在,让我们来更详细的了解路由器的基本概念。
路由出口(the router-outlet)
router-outlet 是路由库中提供的一个指令,路由器根据当前浏览器的URL插入相匹配的组件。你可以在你的应用中加入多个outlet来实现高级的路由场景
路由器将把它匹配的任何组件呈现为路由出口的同级组件
路由和路径(routes and paths)
Routes 是一系列定义(对象集)(译者注:也就是路由配置对象的集合),每个对象包含至少一个path属性和一个component属性(或者一个redirectTo属性)。path指URL中确定应该显示的唯一视图的部分,component指需要与path相关联的angular组件。根据我们提供的一个路由定义(通过静态方法RouterModule.forRoot(routes)),路由器能够将用户导航到特定的视图
每个路由将一个URL路径映射到唯一对应的组件
path的值可以为空(''),这表示应用的一个默认路径,空路径通常是应用的开始。
path的值可以使用通配符字符串(**)。如果请求的URL无法匹配路由数组中任何定义的路由,路由器将会选择通配符路由。通配符路由可以用来展示"Not Found"视图,或者在没有路由匹配的情况下重定向到特定的视图
下面是一个路由的例子:
{ path: 'contacts', component: ContactListComponent}
如果将这个路由添加到路由器配置中,当web应用程序的浏览器URL变为 /contacts
时,路由器将呈现ContactListComponent
组件。
路由匹配策略
angular路由器提供了多种不同的路由匹配策略。默认的策略是检查当前浏览器URL是否以定义的path值为前缀。
例如我们的上一个路由:
{ path: 'contacts', component: ContactListComponent}
也可以写成下面的形式:
{ path: 'contacts',pathMatch: 'prefix', component: ContactListComponent}
pathMatch
属性指定了路由匹配策略,默认值是例子中的prefix
.
pathMatch
的另一个值是full
,当指定一个路由的匹配策略为full
时,路由器将检查path的值是否与当前浏览器URL完全匹配:
{ path: 'contacts',pathMatch: 'full', component: ContactListComponent}
路由参数
创建带参数的路由是webapp的一个常见特性,Angular路由器允许你使用一下两种不同的方式访问路由参数:
- 使用ActivatedRoute服务
- 从angular4开始可以使用ParamMap
你可以使用冒号语法创建一个路由参数,下面是一个带有id参数的路由例子:
{ path: 'contacts/:id', component: ContactDetailComponent}
路由守卫
路由守卫是angular的路由器的一个特性,它允许开发者在一个路由被请求时运行一些处理逻辑,并根据运行结果决定允许或者拒绝用户访问此路由。路由守卫普遍被用在用户访问一个页面时,检查用户是否已登录并被授权。
你可以通过实现@angular/router
模块中的CanActive
接口,重写canActivate()
方法来添加路由守卫。canActivate()
方法用于存放是否允许访问路由的处理逻辑的。下面的守卫将始终允许用户访问一个路由:
class MyGuard implements CanActivate {
canActivate() {
return true;
}
}
然后,你可以使用canActive
属性将路由守卫添加到路由上,达到保护路由的目的:
{ path: 'contacts/:id', canActivate: [MyGuard], component: ContactDetailComponent}
导航指令
angular路由器提供了routerLink
指令来创建导航链接。这个指令采用路由中与组件相关联的路径进行导航,形式如下:
Contacts
多路由出口和辅助路由
angular路由器支持在同一应用中使用多个路由出口(outlet)。
每个组件都有一个相关联的主路由,也可以有多个辅助路由。辅助路由是开发者能够同时导航多个路由。
要创建辅助路由,你需要一个被命名的路由出口,与辅助路由相关联的组件将会在这个命名的路由出口渲染。
- 没有
name
属性的路由出口为主路由出口- 除了主路由出口外,所有的路由出口都应该有一个
name
然后,你可以使用outlet
属性指定你想渲染你的组件的那个路由出口:
{ path: "contacts", component: ContactListComponent, outlet: "outlet1" }
创建一个 angular7 Demo
在这一部分,我们将看到一个如何安装和使用angular路由器的实际例子。这里是我们将创建的demo演示以及github项目地址
安装angular7
使用Angular CLI需要Nodejs 8.9+版本,npm 5.5.1+版本,开发前确保你的系统中安装了它们。然后运行下面的命令行安装最新版的Angular CLI
$ npm install -g @angular/cli
这条命令将会全局安装Angular CLI
Note: 你或许想使用sudo
命令全装安装angluar-cli,这取决于你的npm配置
创建一个angular7 项目
简单的运行下面这条命令,就可以创建一个新的项目:
$ ng new angular7-router-demo
CLI会询问你是否想添加路由(输入N拒绝,因为我们将会在demo看到如何手动添加路由),想使用哪种样式表,选择css ,然后按下Enter
。CLI将会创建包含项目必需文件的文件夹目录,安装项目所需的依赖。
创建虚拟后台(fake back-end)服务
因为我们没有真实的后台可以交互,所以我们使用angular-in-memeory-web-api库来创建一个虚拟后台。这个库是angluar用来演示和测试的内存中的web api,它模拟了REST API的CRUD操作.
这个库的工作方式是拦截HttpClient
向远程服务器发送的请求,并将请求重定向到我们创建的本地内存数据存储区。
为了创建一个虚拟后台,我们需要遵循以下步骤:
01 安装
angular-in-memeory-web-api
模块
02 创建一个返回假数据的服务
03 配置应用程序使用虚拟后台
在终端输入下面的命令安装angular-in-memory-web-api
模块:
$ npm install --save angular-in-memory-web-api
然后,生成一个将要使用的后台服务:
ng g s backend
打开 src/app/backend.service.ts
文件,从angular-in-memory-web-api
模块中引入 InMemoryDbService
:
import {InMemoryDbService} from 'angular-in-memory-web-api'
前面生成的BackendService
服务需要实现InMemoryDbService
接口并重写里面的createDb()
方法:
@Injectable({
providedIn: 'root'
})
export class BackendService implements InMemoryDbService{
constructor() { }
createDb(){
let contacts = [
{ id: 1, name: 'Contact 1', email: '[email protected]' },
{ id: 2, name: 'Contact 2', email: '[email protected]' },
{ id: 3, name: 'Contact 3', email: '[email protected]' },
{ id: 4, name: 'Contact 4', email: '[email protected]' }
];
return {contacts};
}
}
我们在BackendService
中简单的创建了一个联系人数组并返回了他们,每一个联系人都有一个id
属性。
最后,我们将InMemoryWebApiModule
模块引入到app.module.ts
中,并提供我们创建的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 { }
接下来创建一个ContactService
服务,它封装了处理联系人的代码:
$ ng g s contact
打开src/app/contact.service.ts
文件,将它修改为下面的内容:
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
中,我们添加了两个方法:
getContacts()
- 获取全部联系人getContact()
- 通过id
获取某一联系人
你可以将 API_URL
设置成任何URL值,因为我们不会使用一个真实后台。所有的请求都将被拦截并发送到我们所创建的虚拟后台中。
创建我们的 Angular组件
在我们学习如何使用不同的路由特性之前,让我们先为我们的项目创建一些组件。
打开终端运行下面的这些命令:
$ ng g c contact-list
$ ng g c contact-detail
这两条命令将会生成一个ContactListComponent
组件和一个ContactListComponent
组将。将它们添加到根模块中(译者注:此模块位于 app.module.ts
文件中)
配置路由模块
在大多数情况下,你会使用Angular CLI创建带路由配置的项目,但在这里,我们将手动添加路由,如此一来我们能够更好的理解angular中的路由是如何工作的。
添加路由模块
我们需要添加AppRoutingModule,它包含我们的应用程序路由和一个路由器出口,Angular将根据浏览器的当前URL插入当前匹配的组件。
我们将会看到:
- 如何为路由创建一个angular模块并引入它
- 如何为不同的组件配置路由
- 如何配置路由出口
首先,让我们在一个 app-routing.module.ts
中创建路由模块,在src/app
路径下使用如下命令创建app-routing.module.ts
文件:
$ cd angular7-router-demo/src/app
$ touch app-routing.module.ts
译者注:
touch
可能是作者使用的linux创建文件的命令,读者可以直接在src/app
目录下使用angular中自带的ng g m app-routing
命令创建此路由模块文件,然后根据作者下面的步骤修改此文件即可
打开app-routing.module.ts
文件,将它修改为如下内容:
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
,这是一个用来创建Angular模块的TypesScript装饰器。
我们还从@angular/core
包引入了RouterModule
类和Routes
类。
RouterModule
提供了像RouterModule.forRoot()
这样的静态方法,用于将配置对象传递个路由器。
接下来,我们定义了一个类型为Routes
的常量数组routes
用来存放每一个路由的信息。
最后,我们创建并导出了一个名为AppRoutingModule
的模块(你可以起任何你喜欢的名字),这个模块是一个由@NgModule
装饰器装饰的简单的TypeScript类,@NgModule 装饰器接受一个元数据对象,该对象的属性用来描述这个模块。在这个对象的 imports
属性中,我们调用了RouterModule.forRoot(routes)
方法,并将路由数组作为参数传了进去。在exports
数组中,我们添加了RouterModule
.
引入路由模块
接下来,我们将刚才创建的路由模块引入位于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
数组中。
添加路由出口
最后,我们需要添加路由出口(router outlet)。打开包含了主应用模板的src/app/app.component.html
文件,添加
:
Angular路由器将在这里渲染与当前浏览器路径相对应的组件。
以上是在angular项目中手动配置路由所需要遵循的所有步骤。
创建路由
现在,让我们给我们的两个组件添加路由。打开src/app/app-routing.module.ts
,将下面的路由添加到routes
数组中:
const routes: Routes = [
{path: 'contacts' , component: ContactListComponent},
{path: 'contact/:id' , component: ContactDetailComponent}
];
要确保在路由模块中引入了这两个组件:
import { ContactListComponent } from './contact-list/contact-list.component';
import { ContactDetailComponent } from './contact-detail/contact-detail.component';
现在,我们就可以通过/contacts
和contact:id
访问这两个组件了。
添加导航链接
接下来,让我们使用routerLink
指令将导航链接添加到我们的app模板上。打开src/app/app.component.html
,在
上面添加如下代码:
Contacts
接下来,我们需要在ContactListComponent
组件中展示联系人列表。打开 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;
})
}
}
我们创建了一个contacts
数组来存放联系人数据,接下来,我们在该组件中注入ContactService
服务,在ngOnInit
钩子函数中调用服务实例的getContacts()
方法来获取联系人数据(前文BackendService
中创建的fake data)并将它赋值给contacts
数组。
下一步,打开src/app/contact-list/contact-list.component.html
文件,添加如下代码:
Name
Email
Actions
{{ contact.name }}
{{ contact.email }}
Go to details
我们遍历contacts
数组并展示每一个联系人的姓名和email.同时,我们也使用routerLink
指令创建了一个指向每一个联系人详情组件的链接(Go to details
)。
下面是组件截屏:
当我们点击Go to detail
链接,路由器会将我们导航到ContactDetailsComponent
组件。导航路径中有一个id
参数,让我们看看如何在详情组件中访问这个参数。
打开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;
})
});
}
}
我们在组件中注入了ContactService
和ActivatedRoute
,在钩子函数ngOnInit()
中检索将从路由中传递过来的id
属性,并使用它获取我们分配给contact
对象的联系人详细信息。
打开src/app/contact-detail/contact-detail.component.html
文件,添加:
Contact # {{contact.id}}
Name: {{contact.name}}
Email: {{contact.email}}
当我们第一次从127.0.0.1:4200/
访问我们的应用时,路由出口不会渲染任何组件,因此让我们向路由数组中添加如下路由来将空路径重定向到contacts
:
{path: '', pathMatch: 'full', redirectTo: 'contacts'}
我们想匹配完全空的路径,所以我们将路由匹配策略指定为full
.
总结
在本教程中,我们了解了如何使用Angular路由器向应用程序中添加路由和导航。我们了解了很多概念,例如路由出口,路由和路径,并且我们创建了一个demo来实际展示这些不同的概念。你可以在github上获取这些代码。
关于本文
作者:@Ahmed
原文:https://www.smashingmagazine.com/2018/11/a-complete-guide-to-routing-in-angular/