为什么使用路由? |
说通俗一些:
- 在地址栏输入URL时,浏览器会导航到相应页面;
- 在页面中点击链接,浏览器会导航到新页面;
- 点击浏览器的前进和后退按钮,会在你的浏览历史向前或向后导航
那么,在Angular中,是什么决定上述行为的呢?又是如何实现的呢?
在Angular中,对于一个新建的项目而言,只存在一个AppComponent组件,如果不增加其他组件,意味着所有的行为都在这一个组件中完成,单一的组件往往无法保存状态的变化,所以,通常情况下,我们会新建组件,并配置路由。路由把这些组件联络起来,是树形结构的。有了它,就可以在angular中实现导航模式,所以,在angular中,路由是非常重要的部分,组件的实例化与销毁,模块的加载等都与它有关。
那么,如果不配置路由,所有的行为都使用同一个URL,会有什么后果呢?
刷新页面后,无法保留你的应用中的位置。
不能为页面添加标签,方便以后返回相同的页面。
无法与他人共享当前页面的URL。
创建路由项目 |
一、新建一个路由项目,命令:
ng new 项目名 --routing
建完看到这样的目录出现说明项目建立成功。
二、在已有项目上添加路由,命令:
npm install @angular/router -s
在src/app目录下新建:app-routing.module.ts 文件
并在此新建文件中添加代码:
import{NgModule}from'@angular/core';
import{Routes,RouterModule}from'@angular/router';
constroutes:Routes=[];
@NgModule({
imports:[RouterModule.forRoot(routes)],
exports:[RouterModule]
})
exportclassAppRoutingModule{}
配置路由 |
在Angualr中,我们通过将路由映射到处理它们的组件来配置路由。
我们来创建一个具有多种路由的小型应用程序,在这里例子应用程序中,我们将有三种路由:
- 主页,使用 /home 路径;
- 关于产品,使用 /product 路径;
- 关于错误页面,使用 /code404 路径。
Angular路由的组成部件
Routes:描述了应用程序支持的路由配置
RouterOutlet:这是一个“占位符”组件,用于告诉 Angular 要把每个路由的内容放在哪里
RouterLink 指令:用于创建各种路由链接
ActivatedRoute:当前激活的路由对象,保存当前路由的信息,如路由地址,路由参数等。
现在,我们正式开始配置路由。
一、首先,我们需要新建三个组件(home,product,code404),配置这些组件的路由。新建组件的命令:
//注意,新建组件名要在src/app路径下
ng g component 组件名
二、配置路由
在 app-routing.module.ts 文件中进行配置:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HomeComponent} from "./home/home.component";
import {ProductComponent} from "./product/product.component";
import {Code404Component} from "./code404/code404.component";
const routes: Routes = [
{path:'',component:HomeComponent},
{path:'product',component:ProductComponent},
{path:'**',component:Code404Component}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
这时,在 app.component.html 中出现了 router-outlet 标签,就是上面提到的“占位符”组件,也被称为“插座”,用来表示,页面在此处渲染。
此时,路由已配置好,还需要在 app.component.html 中创建链接,让我们点击链接进行跳转,以看到配置路由的效果。
<a [routerLink]="['/']">主页</a>
<a [routerLink]="['/product']">商品详情</a>
//插座组件
<router-outlet></router-outlet>
三、展示配置效果
配置路由参数 |
而现实中,我们浏览购物网站,每点击一个商品,都会将此商品的 id 作为参数上传到URL进行商品详细信息页面的跳转。在 Angular 中,实现此功能就要用到了路由参数。而实现路由参数不止一种办法,在这里,介绍两种方法来实现。
一、在查询参数时传递数据
1、在 app.component.html 中 添加参数。
<a [routerLink]="['/']">主页</a><br>
<a [routerLink]="['/product']" [queryParams]="{id:1}">商品1详情</a><br>
<a [routerLink]="['/product']" [queryParams]="{id:2}">商品2详情</a><br>
<a [routerLink]="['/product']" [queryParams]="{id:3}">商品3详情</a>
<router-outlet></router-outlet>
2、使用 ActivatedRoute 指令接收参数
此时,已经实现了传递参数的效果。但为了能够接收到路由参数,这里用到了上面提到过的 ActivatedRoute 指令,ActivatedRoute是专门为了保存信息的指令,如路由地址,路由参数。
在 product.component.ts 文件中进行接收参数
import { Component, OnInit } from '@angular/core';
import {ActivatedRoute} from "@angular/router";
@Component({
selector: 'app-product',
templateUrl: './product.component.html',
styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {
//声明number类型的变量
private productId:number;
constructor(private routeInfo:ActivatedRoute) {}
ngOnInit() {
this.productId=this.routeInfo.snapshot.queryParams["id"];
}
}
3、展示加了参数的路由效果
我们来看看出现了什么问题,URL地址的参数是正确的,下方接收的商品 id ,只有第一次点击时是正确的,再次点击就获取不到 id 了。(如何解决此问题,请往下看)
二、在URL中传递参数
1、在 app-routing.module.ts 文件中改变 path 属性,让其携带参数。
const routes: Routes = [
{path:'',component:HomeComponent},
//让product携带参数id
{path:'product/:id',component:ProductComponent},
{path:'**',component:Code404Component}
];
2、在 app.component.html 中添加 routerlink 属性的参数
<a [routerLink]="['/']">主页</a><br>
<a [routerLink]="['/product',1]">商品1详情</a><br>
<a [routerLink]="['/product',2]">商品2详情</a><br>
<a [routerLink]="['/product',3]">商品3详情</a>
<router-outlet></router-outlet>
3、为了解决上述 商品id 接收不到的问题,在 product.component.ts 文件中 设置productId 的值。
ngOnInit() {
this.routeInfo.params.subscribe((params:Params)=>this.productId=params["id"]);
this.productId=this.routeInfo.snapshot.queryParams["id"];
}
4、 展示路由参数效果
重定向路由 |
不知道大家有没有注意到我们将 home 组件的 路由路径配置为了 ’ ',现在需求是 客户输入 http://localhost:4200/home 与 输入 http://localhost:4200 都跳转到主页面。这就要用到 重定向路由。
1、我们将 app-routing.module.ts 中的 路由配置更改如下:
const routes: Routes = [
{path:'',redirectTo:'/home',pathMatch:'full'},
{path:'home',component:HomeComponent},
//让product携带参数
{path:'product/:id',component:ProductComponent},
//注意:通配符路由一般放到路由配置的最后,防止被错误匹配
{path:'**',component:Code404Component}
];
2、将 app.component.html 中标签跳转的地址改为 ‘/home’
<a [routerLink]="['/home']">主页</a><br>
<a [routerLink]="['/product',1]">商品1详情</a><br>
<a [routerLink]="['/product',2]">商品2详情</a><br>
<a [routerLink]="['/product',3]">商品3详情</a>
<router-outlet></router-outlet>
3、重定向路由-结果展示
效果:
可以清晰的看到,当删掉URL中的 '/home’时,浏览器会自动跳转到 home页面,这就是重定向路由。这也就是为什么我们在浏览器URL地址栏输入:‘百度’,‘baidu’ 等,不是完整的域名地址时,会自动跳转到 https://www.baidu.com 正确的域名地址。
嵌套路由 |
在商品详情页面,除了显示商品 id 信息,还显示了商品描述,和销售员的页面。通过子路由实现商品描述组件和销售员信息组件展示在商品详情组件内部。
1、新建两个组件:productInfo组件,sellerInfo组件
2、在配置路由文件中 配置子路由
const routes: Routes = [
{path:'',redirectTo:'/home',pathMatch:'full'},
{path:'home',component:HomeComponent},
//让product携带参数
//给/product路由添加 children 属性 设置 子路由
{path:'product/:id',component:ProductComponent,children:[
{path:'',component:ProductInfoComponent},
{path:'seller/:id',component:SellerInfoComponent}
]},
{path:'**',component:Code404Component}
];
3、在 sellerInfo 组件 中,接收 sellerId
import { Component, OnInit } from '@angular/core';
import {ActivatedRoute} from "@angular/router";
@Component({
selector: 'app-seller-info',
templateUrl: './seller-info.component.html',
styleUrls: ['./seller-info.component.css']
})
export class SellerInfoComponent implements OnInit {
private sellerId:number;
constructor(private routeInfo:ActivatedRoute) { }
ngOnInit() {
this.sellerId=this.routeInfo.snapshot.params["id"];
}
}
4、修改 product.html 的模板
注意:这时路径不能配置成 /,需要配置成相对路径的 ./,表示为:在当前路由下的子路由。其中在标签后添加了“插座”,是为了给子路由标记呈现的位置。
<p>product works!</p>
<p>此时浏览的是:商品{{productId}}</p>
<a [routerLink]="['./']">商品描述</a><br>
<a [routerLink]="['./seller',2020100]">销售员信息</a>
<router-outlet></router-outlet>
5、嵌套路由结果展示
注意看URL地址的变化
注意:
可以查看元素结构看到, router-outlet 形成了父子嵌套关系,可以无限嵌套。
插座之间的父子关系–子路由
辅助路由 |
插座之间的兄弟关系–辅助路由
现在,我们单独开发一个聊天室组件,显示在新定义的插座上。
1、新建组件:chat
2、配置路由
//outlet属性,用来标记插座
{path:'chat',component:ChatComponent,outlet:'aux'}
3、在 html页面中添加链接和插座
其中,primary属性是指:当点击开始聊天时,URL地址主路由为home,辅助路由为 chat
<a [routerLink]="[{outlets:{primary:'home',aux:'chat'}}]">开始聊天</a>
<a [routerLink]="[{outlets:{aux:null}}]">结束聊天</a>
//主路由插座
<router-outlet></router-outlet>
//辅助路由插座
<router-outlet name="aux"></router-outlet>
4、添加辅助路由结果展示
5、元素结构
可以清晰的看到两个插座的关系,是兄弟关系,这就是辅助路由。
路由守卫(钩子) |
- 用户已登录并拥有某些权限时才能进入某些路由
- 一个由多个表单组件组成的页面,需要将当前路由的组件中填写了满足要求的信息才可以导航到下一个路由
- 当用户未执行保存操作而试图离开当前导航时提醒用户
Angular 提供了一些钩子帮助控制进入或离开路由,这些钩子也就是路由守卫。
- CanActivate:处理导航到某路由的情况
- CanDeactivate:处理从当前路由离开的情况
- Resolve:在路由激活之前获取路由数据
一、CanActivate
我们来模拟一个 登录守卫 ,当未登录时,不可访问其他路由。
1、在 src/app 路径下新建 guard 文件夹,在此文件夹下新建 ts 文件,命名为:login.guard.ts
2、在 login.guard.ts 文件中新建 路由守卫,LoginGuard 类实现 CanActivate接口,返回true或false,angular根据返回值判断请求通过不通过。
//代码逻辑大概是:生成一个0到1的随机数,随机数小于0.5,代表用户登录,大于等于0.5代表用户未登录。
import {CanActivate} from "@angular/router";
export class LoginGuard implements CanActivate{
canActivate(){
let loggedIn:boolean=Math.random()<0.5;
if (!loggedIn){
console.log("用户未登录!");
}
return loggedIn;
}
}
3、配置product路由,先把 LoginGuard 加入 providers,再指定路由守卫。其中,CanActivate可以指定多个路由守卫,值是一个数组。
const routes: Routes = [
{path:'',redirectTo:'/home',pathMatch:'full'},
{path:'home',component:HomeComponent},
//让product携带参数
{path:'chat',component:ChatComponent,outlet:'aux'},
{path:'product/:id',component:ProductComponent,children:[
{path:'',component:ProductInfoComponent},
{path:'seller/:id',component:SellerInfoComponent}
],canActivate:[LoginGuard]}, //指定路由守卫
{path:'**',component:Code404Component}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers:[LoginGuard]
})
4、显示效果
二、CanDeactivate
离开时候的路由守卫,提醒用户执行保存操作后才能离开。
1、在 guard 文件夹下 新建文件:unsaved.guard.ts ,新建离开路由守卫
import {CanDeactivate} from "@angular/router";
import {ProductComponent} from "../product/product.component";
export class UnsavedGuard implements CanDeactivate<ProductComponent>{
canDeactivate(component:ProductComponent){
return window.confirm("您还未保存,确定要离开吗?");
}
}
2、配置此守卫路由
const routes: Routes = [
{path:'',redirectTo:'/home',pathMatch:'full'},
{path:'home',component:HomeComponent},
//让product携带参数
{path:'chat',component:ChatComponent,outlet:'aux'},
{path:'product/:id',component:ProductComponent,children:[
{path:'',component:ProductInfoComponent},
{path:'seller/:id',component:SellerInfoComponent}
],canActivate:[LoginGuard],
canDeactivate:[UnsavedGuard]}, //离开守卫路由
{path:'**',component:Code404Component}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers:[LoginGuard,UnsavedGuard]
})
3、配置离开路由结果展示
效果:
点击 ‘确定’ ,离开页面;
点击 ‘取消’ ,停留此页面
三、Resolve路由
Resolve路由是指在路由激活之前获取路由数据。
http请求数据返回有延迟,导致模板无法立刻显示。数据返回之前模板上所有需要用插值表达式显示的值都是空的,用户体验感极差。有了Resolve路由,在进入路由之前去服务器读数据,把需要的数据读好,带着数据进到路由,立刻把数据显示出来。
我们来模拟一个 Resolve守卫。
1、在 product.ts 文件中新建类,用来传商品id和商品名称
export class Product {
constructor(public id:number,public name:string) {
}
}
2、在 guard 文件夹下新建 product.resolve.ts 文件,创建Resolve路由
import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from "@angular/router";
import {Observable} from "rxjs";
import {Injectable} from "@angular/core";
@Injectable() //注解
export class ProductResolve implements Resolve<Product>{
constructor(private router:Router) {
}
resolve(route:ActivatedRouteSnapshot,state:RouterStateSnapshot):Observable<Product>|Promise<Product> |Product{
let productId:number=route.params["id"];
if (productId==1){
return new Product(1,"iphone11");
}else{
this.router.navigate(['/home']);
return undefined;
}
}
}
//逻辑大意:商品id为1时,返回商品信息,不是1时,跳转到主页面。
3、在product 路由里配置此Resolve 路由,并加入到Provider 里声明。
const routes: Routes = [
{path:'',redirectTo:'/home',pathMatch:'full'},
{path:'home',component:HomeComponent},
//让product携带参数
{path:'chat',component:ChatComponent,outlet:'aux'},
{path:'product/:id',component:ProductComponent,children:[
{path:'',component:ProductInfoComponent},
{path:'seller/:id',component:SellerInfoComponent}
],canActivate:[LoginGuard], //登录守卫路由
canDeactivate:[UnsavedGuard] //离开守卫路由
,resolve:{ //Resolve守卫路由
product:ProductResolve //Resolve 是一个对象,对象里参数的名字就是想传入参数的名字product,用ProductResolve解析生成
}},
{path:'**',component:Code404Component}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers:[LoginGuard,UnsavedGuard,ProductResolve]
})
4、Resolve 路由结果展示
效果:
点击商品 id 为1 的商品,会返回一条商品数据;
而点击其他商品跳转到主页面。
tips:
所有和路由相关的信息都是在模块层进行配置的。组件内部并不知道路由的信息。