1.在app.component.ts文件定义变量
// 路由列表
tagsList: Array<{ title: string, path: string, permission: string, show: boolean, componentName: string, isSelect: boolean }> = [];
并在ngOnInit方法中加入以下代码
this.router.events
.pipe(
filter(event => event instanceof NavigationEnd), // 筛选原始的Observable:this.router.events
map(() => this.activatedRoute),
map(route => {
while (route.firstChild) {
route = route.firstChild;
}
return route;
}),
mergeMap(route => route.data)
)
.subscribe((data) => {
const url = this.router.url;
const isLogin = StorageUtil.getItem(StorageUtil.storageKey.isLogin);
// 未登录/会话过期或者是404,403等错误页面,删除全部页面的路由缓存,并且清空标签列表
if (!isLogin || isLogin === 'false' || url.indexOf('/common/errors') >=0) {
SimpleReuseStrategy.deleteAllRouteSnapshot();
this.tagsList = [];
} else {
// 路由data的标题
let title = data['title'];
const path = data['path'];
// 路径参数
const param = this.router['browserUrlTree'] && this.router['browserUrlTree'].queryParams ? this.router['browserUrlTree'].queryParams : {};
// 设置tags标题
if(param && param['taskId']){
if(url.indexOf('/task/taskDetail') >= 0){
title = (param['clientName'] ? param['clientName'] : title) + '[' + param['taskId'] + ']'
} else if(param && param['isUpdateTime'] && url.indexOf('/task/analysisPerson') >= 0){
title = '耗时修改' + '[' + param['taskId'] + ']'
} else{
title = title + '[' + param['taskId'] + ']'
}
}
// 关闭指定页面
let colsePage = '';
if(window['colsePage']){
colsePage = window['colsePage']
}
this.tagsList = this.tagsList.filter(p => {
if (p.path && p.path === url) {
p.isSelect = true;
}else{
p.isSelect = false;
}
if(colsePage == p.path){
// 删除指定关闭页面的路由缓存
let colsePath = colsePage.replace(/\//g, '_') + '_' + p.componentName;
SimpleReuseStrategy.deleteRouteSnapshot(colsePath);
return false;
}else {
return true;
}
});
window['colsePage'] = '';
const menu = {
title: title,
path: url,
permission: data['permission'],
show: data['show'],
componentName: data['componentName'] ? data['componentName'] : '',
isSelect: true
};
this.titleService.setTitle(title);
const exitMenu = this.tagsList.find(info => info.path === url);
if (exitMenu) { // 如果存在不添加,当前表示选中
this.tagsList.forEach(p => p.isSelect = p.path === url);
return;
}
if (exitMenu) { // 如果存在不添加,当前表示选中
return;
}
if (menu.show) {
this.tagsList.push(menu);
}
}
});
2.重写angular路由复用策略
import {
RouteReuseStrategy, DefaultUrlSerializer, ActivatedRouteSnapshot, DetachedRouteHandle,
Route
} from '@angular/router';
/**
* 重写angular路由重用策略,路由重用(多标签页实现)和路由懒加载最终解决方案
*/
export class SimpleReuseStrategy implements RouteReuseStrategy {
public static handlers: { [key: string]: DetachedRouteHandle } = {};
private static waitDelete: string;
/** 表示对所有路由允许复用,返回true,说明允许复用, 如果你有路由不想利用可以在这加一些业务逻辑判断 */
public shouldDetach(route: ActivatedRouteSnapshot): boolean {
//Avoid second call to getter(避免第二次调用getter)
let config: Route = route.routeConfig && route.data['keepAlive'];
//Don't store lazy loaded routes(不存储延迟加载的路由)
return config && !config.loadChildren;
}
/** 当路由离开时会触发。按path作为key存储路由快照&组件当前实例对象 */
public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
if (SimpleReuseStrategy.waitDelete && SimpleReuseStrategy.waitDelete == this.getRouteUrl(route)) {
//如果待删除是当前路由则不存储快照
SimpleReuseStrategy.waitDelete = null;
return;
}
SimpleReuseStrategy.handlers[this.getRouteUrl(route)] = handle;
/*
This is where we circumvent the error.
Detached route includes nested routes, which causes error when parent route does not include the same nested routes
To prevent this, whenever a parent route is stored, we change/add a redirect route to the current child route
(这是我们规避错误的地方。
分离的路由包括嵌套路由,当父路由不包含相同的嵌套路由时会导致错误
为了防止这种情况,无论何时存储父路由,我们都会将重定向路由更改/添加到当前子路由)
*/
let config: Route = route.routeConfig;
if(config) {
let childRoute: ActivatedRouteSnapshot = route.firstChild;
let futureRedirectTo = childRoute ? childRoute.url.map(function(urlSegment) {
return urlSegment.path;
}).join('/') : '';
let childRouteConfigs: Route[] = config.children;
if(childRouteConfigs) {
let redirectConfigIndex: number;
let redirectConfig: Route = childRouteConfigs.find(function(childRouteConfig, index) {
if(childRouteConfig.path === '' && !!childRouteConfig.redirectTo) {
redirectConfigIndex = index;
return true;
}
return false;
});
//Redirect route exists(重定向路由存在)
if(redirectConfig) {
if(futureRedirectTo !== '') {
//Current activated route has child routes, update redirectTo(当前激活的路由有子路由,更新redirectTo )
redirectConfig.redirectTo = futureRedirectTo;
} else {
//Current activated route has no child routes, remove the redirect (otherwise retrieval will always fail for this route)(当前激活的路由没有子路由,删除重定向(否则此路由的检索将始终失败))
childRouteConfigs.splice(redirectConfigIndex, 1);
}
} else if(futureRedirectTo !== '') {
childRouteConfigs.push({
path: '',
redirectTo: futureRedirectTo,
pathMatch: 'full'
});
}
}
}
}
/** 若 path 在缓存中有的都认为允许还原路由 */
public shouldAttach(route: ActivatedRouteSnapshot): boolean {
return !!SimpleReuseStrategy.handlers[this.getRouteUrl(route)]
}
/** 从缓存中获取快照,若无则返回nul */
public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
let config: Route = route.routeConfig;
//We don't store lazy loaded routes, so don't even bother trying to retrieve them(我们不会存储懒加载路线,所以甚至不试图检索他们)
if(!config || config.loadChildren) {
return false;
}
return SimpleReuseStrategy.handlers[this.getRouteUrl(route)]
}
/** 进入路由触发,判断是否同一路由 */
public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
let isReUsed = future.routeConfig===curr.routeConfig &&
JSON.stringify(future.params)==JSON.stringify(curr.params);
// 如果路由未来路由url(即要跳转的路由url)和当前路由url(即要离开的路由url)一致,返回false
if(isReUsed && future && future.routeConfig && curr && curr.routeConfig && future.routeConfig.data['path'] && future.routeConfig.data['path'] == curr.routeConfig.data['path']){
isReUsed = false
}
return isReUsed;
}
/** 根据路由url并加以处理 **/
private getRouteUrl(route: ActivatedRouteSnapshot) {
// todo 因为环境中使用了路由懒加载,返回路径最好带上组件名,防止路由报错->(Cannot reattach ActivatedRouteSnapshot created from a different route)
// 这句代码可以获取当前路由的组件名componentName,但生成环境(打包)将组建名缩写成随机单个字母,所以需要手动通过route.routeConfig.data['componentName']去获取在路由上自定义的组件名
let componentShortName = (route.routeConfig.loadChildren || route.routeConfig.component.toString().split('(')[0].split(' ')[1] );
if(route.routeConfig.data && route.routeConfig.data['componentName']){
componentShortName = route.routeConfig.data['componentName'];
}
return route['_routerState'].url.replace(/\//g, '_')
+ '_' + componentShortName;
}
/** 根据路由缓存key,删除快照 **/
public static deleteRouteSnapshot(name: string): void {
if (SimpleReuseStrategy.handlers[name]) {
delete SimpleReuseStrategy.handlers[name];
} else {
SimpleReuseStrategy.waitDelete = name;
}
}
/** 删除全部快照 **/
public static deleteAllRouteSnapshot(): void {
SimpleReuseStrategy.handlers = {}
}
}
3.在app.module.ts文件中加入路由复用策略类SimpleReuseStrategy
4.自定义tags组件,通过@input获取父组件的tagsList属性。更改tagsList属性后,通过@output向父组件传递tagsList数据
tags组件ts关键代码
/**
* 关闭当前标签页
* @param index
*/
closeTags(index) {
const delItem = this.tagsList.splice(index, 1)[0];
const item = this.tagsList[index] ? this.tagsList[index] : this.tagsList[index - 1];
if (item) {
if (delItem.path === this.router.url) {
this.router.navigateByUrl(item.path);
}
// 删除复用
let path = delItem.path.replace(/\//g, '_') + '_' + delItem.componentName;
SimpleReuseStrategy.deleteRouteSnapshot(path);
} else {
this.router.navigateByUrl('/user/person');
}
// 通过emit将信息发射出去
this.tagsEvent.emit(this.tagsList);
}
/**
* 关闭其他标签
*/
closeOther(){
this.router.navigateByUrl(this.router.url);
const curItem = this.tagsList.filter(item => {
if(item.path === this.router.url){
return true;
}else{
// 删除复用
let path = item.path.replace(/\//g, '_') + '_' + item.componentName;
SimpleReuseStrategy.deleteRouteSnapshot(path);
return false;
}
return item.path === this.router.url;
});
this.tagsList = curItem;
// 通过emit将信息发射出去
this.tagsEvent.emit(this.tagsList);
}
/**
* 关闭全部标签
*/
closeAll(){
const curItem = this.tagsList.filter(item => {
// 删除复用
let path = item.path.replace(/\//g, '_')+ '_' + item.componentName;
SimpleReuseStrategy.deleteRouteSnapshot(path);
});
this.tagsList = [];
// 通过emit将信息发射出去
this.tagsEvent.emit(this.tagsList);
this.router.navigateByUrl('/user/person');
}
5.最终实现效果图如下: