一、SPA
单页Web应用(single page web application,SPA),就是只有一张Web页面的应用。单页应用程序 (SPA) 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。 [1] 浏览器一开始会加载必需的HTML、CSS和JavaScript,所有的操作都在这张页面上完成,都由JavaScript来控制。因此,对单页应用来说模块化的开发和设计显得相当重要。
特点
速度:更好的用户体验,让用户在web app感受native app的速度和流畅,
MVC:经典MVC开发模式,前后端各负其责。
ajax:重前端,业务逻辑全部在本地操作,数据都需要通过AJAX同步、提交。
路由:在URL中采用#号来作为当前视图的地址,改变#号后的参数,页面并不会重载。
单页Web应用(single page web application,SPA)是当今网站开发技术的弄潮儿,很多传统网站都在或者已经转型为单页Web应用,新的单页Web应用网站(包括移动平台上的)也如雨后春笋般涌现在人们的面前,如Gmail、Evernote、Trello等。如果你是一名Web开发人员,却还没开发过或者甚至是没有听说过单页应用,那你已经Out很久了。
单页Web应用和前端工程师们息息相关,因为主要的变革发生在浏览器端,用到的技术其实还是HTML+CSS+JavaScript,所有的浏览器都原生支持,当然有的浏览器因为具备一些高级特性,从而使得单页Web应用的用户体验更上一层楼。关于单页应用的优点和缺点,网上讲解的文章有很多,这里就不展开论述了。 单页Web应用,顾名思义,就是只有一张Web页面的应用。浏览器一开始会加载必需的HTML、CSS和JavaScript,之后所有的操作都在这张页面上完成,这一切都由JavaScript来控制。因此,单页Web应用会包含大量的JavaScript代码,复杂度可想而知,模块化开发和设计的重要性不言而喻。
二、路由基础
1.新建项目
ng new router --routing
2.基础知识
名称 | 简介 |
---|---|
Routes | 路由配置,保存着哪个URL对应展示哪个组件,以及在哪个RouterOutlet中展示组件。 |
RouterOutlet | 在Html中标记路由内容呈现位置的占位符指令。 |
Router | 负责在运行时执行路由的对象,可以通过调用其navigate()和navigateByUrl()方法来导航到一个指定的路由。 |
RouterLink | 在Html中声明路由导航用的指令。 |
ActivedRoute | 当前激活的路由对象,保存着当前路由的信息,如路由地址,路由参数等。 |
3.路由配置
-
Routes
新建home、product组件配置到路由中,注意path不要以/开头。
路由配置.png -
RouterOutlet
router-outlet后显示路由内容.png -
RouterLink
以 / 开头表示路由到根路由
使用routerLink.png -
Router
html文件中绑定事件
html导航配置.png
ts文件中使用navigate方法
ts导航事件.png 通配符路由
通配符路由的path是两个星号(**),它会匹配任何URL。当路由器匹配不上任何路由时会选择这个路由。
新建404组件
ng g component code404
app-routing.module中配置
运行效果
注意:路由器使用先匹配者优先的策略来选择路由。通配符路由通常是路由配置中最后一个。
4.路由时传递数值
- 在查询参数中传递数据
/product?id=1&name=2 => ActivatedRoute.queryParams[id]
- 在路由路径中传递数据
{path:' /product/:id'} => /product/1 => ActivatedRoute.queryParams[id]
- 在路由配置中传递数据
{path:' /product/:id',component:'ProductComponent',data:[{isProd:true}]
=> ActivatedRoute.data[0][isProd]
参数快照和参数订阅
修改toProductDetails方法
这时我们点击按钮
看似没什么问题,这时我们再点击商品详情链接
问题出现了,从home组件路由到product组件时候,product组件被创建,这时它的的constructor方法被调用,ngOnInit()被调用一次,页面上的参数id会根据URL信息拿到正确的参数。当我们从product组件又调到product组件时,由于在product组件已经被创建过一次了,所以constructor()方法不会被调用,所以ngOnInit方法也不会再调用 ,所以id属性依然保持着第一次被创建时赋予的值。解决这个问题的方法就是参数订阅
//多参数路由传值写法
<a [routerLink]="['/product',1,33]">商品详情</a>
<a [routerLink]="['/product/1/33']">商品详情</a>
TS文件:nav() {
this.router.navigate(['/product', 2, 666]);
}
路由:{path: 'product/:id/:name', component: ProductComponent},
或
<a [routerLink]="['/product']" [queryParams]="{id:1,name:33}">商品详情</a>
nav() {
this.router.navigate(['/product'], {queryParams: {id: 19, name: 33}});
}
5.重定向路由
在用户访问一个特定的网址时,将其重定向到另一个指定的地址。
www.aaa.com => www.aaa.com/products
www.aaa.com/x => www.aaa.com/y
6.子路由
{path: 'home', component: HomeComponent}
配置子路由
{path: 'home', component: HomeComponent,children:[
{path: '', component: XXXXComponent},
{path: '/YYYY', component: YYYYComponent}
]}
新建两个组件
ng g component sellerInfo
ng g component productDesc
注意:
子路由可以无限嵌套;
路由信息都是模块module中配置的,组件本身并不知道路由相关的信息。
7.辅助路由
1.页面插座,辅助路由的写法是带有name属性
<router-outlet></router-outlet>
<router-outlet name="msg"></router-outlet>
2.路径配置,名为msg的辅助路由可以用来显示XXX组件和YYY组件
{ path:'XXX',XXXcomponent,outlet:'msg' }
{ path:'YYY',YYYcomponent,outlet:'msg' }
3.路由参数跳转路由,primary控制主路由,不管在哪个页面,点击后主路由都会显示home组件
<a [routerLink]="[{outlets:{primary:'home',msg:'XXX'}}]">开始咨询</a>
主路由-辅助路由。路由的配置:名为msg的辅助路由可以显示xxx和yyy组件。点击Xxx时,主路由显示home组件,辅助路由显示XXX组件
demo示例
在app组件上再定义一个插座来显示聊天面板。
单独开发一个聊天组件,只显示在新定义的插座上。
ng g component chat
通过路由参数控制新插座是否显示聊天面板。
另一种方式
8.路由守卫
也就是,拦截器,在进入或离开路由前执行一些逻辑
应用场景
- 只有当用户已经登陆并拥有某项权限时才进入某路由
- 一个由多个表单组件组成的向导,例如注册流程,用户只有在当前组件填写了满足要求的信息才进入下一个路由。
- 当用户未执行保存操作而试图离开当前导航时提醒用户。
(1)CanActivate : 处理导航到某路由之前的情况,如:付费用户
一个用来定义类的接口,路由器会首先调用它来决定是否应该激活该组件。应该返回布尔值或能解析为布尔值的可观察对象(Observable)或承诺(Promise)。
canActivate demo
canActivate demo2
//1.编写CanActivate守卫PermissionGuard
// 在guard/permission.guard.ts中
//导入CanActivate守卫
import { CanActivate } from '@angular/router';
export class PermissionGuard implements CanActivate {
canActivate(){
var hasPermission:boolean = Math.random() > 0.5;
if (!hasPermission) {
console.log('用户无权限访问');
};
return hasPermission;
//返回值true时进入路由
}
}
//2.在路由配置中增加canActivate属性
// 在app-routing.module.ts中
//导入PermissionGuard
import { PermissionGuard } from './guard/permission.guard';
const routes: Routes = [{path:'stocks/:id',component:StocksComponent,canActivate:[PermissionGuard]}]
//3.写入providers
// 在app.module.ts中
//先导入
import { PermissionGuard } from './guard/permission.guard';
providers: [PermissionGuard]
(2)CanDeactivate : 处理从当前路由离开的情况,如:是否未保存就离开
一个用来定义类的接口,路由器在导航后会首先调用它来决定是否应该取消该组件的激活状态。应该返回布尔值或能解析为布尔值的可观察对象(Observable)或承诺(Promise)。
返回布尔值为true时离开路由。
CanDeactivate demo2
//1.编写CanDeactivate守卫focusGuard
// 在guard/permission.guard.ts中
//导入CanDeactivate守卫和要离开的组件
import { CanDeactivate } from '@angular/router';
import { StocksComponent } from '../stocks/stocks.component';
//判断component上isfocus是否已关注,未关注就弹出窗口
export class focusGuard implements CanDeactivate<StocksComponent> {
canDeactivate(component:StocksComponent){
if(!component.isfocus){
return window.confirm('不关注吗')
}else{
return true;
}
}
}
//2.在stocks.component.html (要守卫的组件)上写按钮
<button (click)="focus()">关注</button>
//3.在stocks.component.ts 写逻辑
focus(){
this.isfocus = true;
}
//4.在路由配置中增加canDeactivate属性
// 在app-routing.module.ts中
//5.导入focusGuard
import { focusGuard } from './guard/focus.guard';
const routes: Routes = [{path:'stocks/:id',component:StocksComponent,canActivate:[PermissionGuard],canDeactivate:[focusGuard]}]
//6.写入providers
// 在app.module.ts中
//先导入
import { focusGuard } from './guard/focus.guard';
providers: [PermissionGuard,focusGuard]
(3)Resolve : 在路由激活之前获取路由数据,提高用户体验
一个用来定义类的接口,路由器会在渲染该路由之前先调用它来解析路由数据。应该返回一个值或能解析为值的可观察对象(Observable)或承诺(Promise)。
Resolve demo2
在进入路由{path:”stock/:id”}时,当id=1时,实例化stock类
//1.在stockComponent中编写stock类的构造函数
export class StocksComponent implements OnInit {
private stock:Stock;
constructor() { }
ngOnInit() {
}
}
export class Stock{
constructor(public id:number,public name:string){}
}
//2.编写Resolve:stockResolve,返回值将传入对应path的Resolve属性中,当id=1时,实例化stock并返回
//注意导入的内容
import { Resolve,ActivatedRouteSnapshot,RouterStateSnapshot,Router } from '@angular/router';
import { Stock } from '../stocks/stocks.component';
import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';
//@Injectable() 使constructor中的依赖注入生效,组件有@Component注解所以不用写@Injectable()
@Injectable()
export class stockResolve implements Resolve<Stock>{
constructor(private router:Router){}
resolve(route: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<Stock>|Promise<Stock>|Stock {
let id = route.params['id'];
//路由路径id为1时
if(id == 1){
console.log('这里是1');
return new Stock(1,'IBM'); // 返回实例化值 stock
}else{
this.router.navigate(['/home']);
return undefined;
}
}
}
//3.在路由配置中,path路径中增加Resolve属性:
{path:'stocks/:id',component:StocksComponent,canActivate:[PermissionGuard],canDeactivate:[focusGuard],resolve:{stock:stockResolve}
//resolve对象格式,各种数据。stockResolve守卫传值给stock
}
//4.在stockComponent中的ngOnInit,data数据值来源于path中的stock,stock是Stock类型
ngOnInit() {
this.routerInfo.data.subscribe((data:{stock:Stock})=>{
this.stock = data.stock
console.log(this.stock);
});
}
//5.在provider中写入stockResolve
//先导入
import { stockResolve } from './guard/stock.resolve';
providers: [PermissionGuard,focusGuard,stockResolve ]