import {ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy} from '@angular/router'; import {Injectable} from '@angular/core';
interface IRouteConfigData { reuse: boolean; }
interface ICachedRoute { handle: DetachedRouteHandle; data: IRouteConfigData; }
@Injectable() export class AppReuseStrategy implements RouteReuseStrategy { private static routeCache = new Map<string, ICachedRoute>(); private static waitDelete: string; // 当前页未进行存储时需要删除 private static currentDelete: string; // 当前页存储过时需要删除
/** 进入路由触发,判断是否是同一路由 */ shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { console.log("shouldReuseRoute",future,curr,future.routeConfig === curr.routeConfig); return future.routeConfig === curr.routeConfig; }
/** 表示对所有路由允许复用 如果你有路由不想利用可以在这加一些业务逻辑判断,这里判断是否有data数据判断是否复用 */ shouldDetach(route: ActivatedRouteSnapshot): boolean { const data = this.getRouteData(route); if (data) { console.log("shouldDetach",route,true); return true; } console.log("shouldDetach",route,false); return false; }
/** 当路由离开时会触发。按path作为key存储路由快照&组件当前实例对象 */ store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void { const url = this.getFullRouteUrl(route); const data = this.getRouteData(route); if (AppReuseStrategy.waitDelete && AppReuseStrategy.waitDelete === url) { // 如果待删除是当前路由,且未存储过则不存储快照 AppReuseStrategy.waitDelete = null; console.log("store",route,handle,null) return null; }else { // 如果待删除是当前路由,且存储过则不存储快照 if (AppReuseStrategy.currentDelete && AppReuseStrategy.currentDelete === url) { AppReuseStrategy.currentDelete = null; console.log("store",route,handle,null) return null; }else { AppReuseStrategy.routeCache.set(url, { handle, data }); this.addRedirectsRecursively(route); console.log("store",route,handle,"store") } } }
/** 若 path 在缓存中有的都认为允许还原路由 */ shouldAttach(route: ActivatedRouteSnapshot): boolean { const url = this.getFullRouteUrl(route); console.log("shouldAttach",AppReuseStrategy.routeCache.has(url)) return AppReuseStrategy.routeCache.has(url); }
/** 从缓存中获取快照,若无则返回nul */ retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle { const url = this.getFullRouteUrl(route); // const data = this.getRouteData(route); console.log("retrieve",route) return AppReuseStrategy.routeCache.has(url) ? AppReuseStrategy.routeCache.get(url).handle : null; }
private addRedirectsRecursively(route: ActivatedRouteSnapshot): void { const config = route.routeConfig; if (config) { if (!config.loadChildren) { const routeFirstChild = route.firstChild; const routeFirstChildUrl = routeFirstChild ? this.getRouteUrlPaths(routeFirstChild).join('/') : ''; const childConfigs = config.children; if (childConfigs) { const childConfigWithRedirect = childConfigs.find(c => c.path === '' && !!c.redirectTo); if (childConfigWithRedirect) { childConfigWithRedirect.redirectTo = routeFirstChildUrl; } } } route.children.forEach(childRoute => this.addRedirectsRecursively(childRoute)); } }
private getFullRouteUrl(route: ActivatedRouteSnapshot): string { let next = route; // Since navigation is usually relative // we go down to find out the child to be shown. while (next.firstChild) { next = next.firstChild; } const segments = []; // Then build a unique key-path by going to the root. while (next) { segments.push(next.url); next = next.parent; } return segments.reverse().join('/');
// return this.getFullRouteUrlPaths(route).filter(Boolean).join('/').replace('/', '_'); }
private getFullRouteUrlPaths(route: ActivatedRouteSnapshot): string[] { const paths = this.getRouteUrlPaths(route); return route.parent ? [ ...this.getFullRouteUrlPaths(route.parent), ...paths ] : paths; }
private getRouteUrlPaths(route: ActivatedRouteSnapshot): string[] { return route.url.map(urlSegment => urlSegment.path); }
private getRouteData(route: ActivatedRouteSnapshot): IRouteConfigData { // return route.routeConfig && route.routeConfig.data as IRouteConfigData; return route.data as IRouteConfigData; }
/** 用于删除路由快照*/ public static deleteRouteSnapshot(url: string): void { if (url[0] === '/') { url = url.substring(1); } url = url.replace('/', '_'); if (AppReuseStrategy.routeCache.has(url)) { AppReuseStrategy.routeCache.delete(url); AppReuseStrategy.currentDelete = url; }else { AppReuseStrategy.waitDelete = url; } } } providers: [ { provide: RouteReuseStrategy, useClass: AppReuseStrategy } ],
//路由列表 menuList: Array<{ title: string, module: string, power: string,isSelect:boolean }>=[]; constructor( private router: Router, private activatedRoute: ActivatedRoute, private titleService: Title) { //路由事件 this.router.events.pipe( filter(event => event instanceof NavigationEnd), map(() => this.activatedRoute), map(route => { while (route.firstChild) route = route.firstChild; return route; }), filter(route => route.outlet === 'primary'), mergeMap(route => route.data) ).subscribe((event) => { //路由data的标题 let title = event['title']; if (event.reuse) { this.menuList.forEach(p => p.isSelect=false); var menu = { title: title, module: event["module"], power: event["power"], isSelect:true}; this.titleService.setTitle(title); let exitMenu=this.menuList.find(info=>info.title==title); if(exitMenu){//如果存在不添加,当前表示选中 this.menuList.forEach(p => p.isSelect=p.title==title); return ; } this.menuList.push(menu); } }); }
ngOnInit() {
}
ngOnChanges() { console.log(this.menuList); }
closeUrl(module: string, isSelect: boolean, event: Event) { event.preventDefault(); // 当前关闭的是第几个路由 const index = this.menuList.findIndex(p => p.module === module); // 如果只有一个不可以关闭 if (this.menuList.length === 1) return; this.menuList = this.menuList.filter(p => p.module !== module); // 删除复用 AppReuseStrategy.deleteRouteSnapshot(module); if (!isSelect) return; // 显示上一个选中 let menu = this.menuList[index - 1]; if (!menu) {// 如果上一个没有下一个选中 menu = this.menuList[index]; } this.menuList.forEach(p => p.isSelect = p.module === menu.module); // 显示当前路由信息 this.router.navigate(['/' + menu.module]); }
<reusetab
.reuseTab-col { display: inline-block; height: 100%; margin: 0 32px 0 0; box-sizing: border-box; position: relative; transition: color 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955); cursor: pointer; text-decoration: none; background-color: white; }
.reuseTab-col:hover { color:#40a9ff; } .reuseTab-col:active { color:#40a9ff; font-weight:500; } .reusetab-nav { display: block; background-color: #fff; padding:16px 8px; border-bottom:1px solid #d9d9d9; outline:none; user-select:none; }
<div class="reusetab-nav"> <div *ngFor="let node of list" class="reuseTab-col" (click)="clickFunc(node)">{{node.title}}</div> </div> import { Component, OnInit, Input, OnChanges } from '@angular/core'; import { Route,Router } from '@angular/router';
@Component({ selector: 'reusetab', templateUrl: './reusetab.component.html', styleUrls: ['./reusetab.component.css'] }) export class ReusetabComponent implements OnInit,OnChanges {
@Input('list') list;
constructor( // private route:Route, private router:Router, ) { }
ngOnInit() { }
ngOnChanges() { console.log(this.list); }
clickFunc(node) { console.log(node); this.router.navigateByUrl(node.module); }
} import {filter,map,mergeMap } from 'rxjs/operators';
filter(event => event instanceof NavigationEnd), map(() => this.activatedRoute), map(route => { while (route.firstChild) route = route.firstChild; return route; }), filter(route => route.outlet === 'primary'), mergeMap(route => route.data)
|