关于在tabset中动态加载Component ,点击查看我之前的文章
介绍了如何使用动态加载Component 的方式实现tabset 标签页,从而达到页面切换。
这种方式缺点就是无法使用Angular 的路由来跳转,多个页面如果如果某个元素的id 值相同会有影响。
以下的方式主要使用了Angular 的路由复用功能—>RouteReuseStrategy
主要借鉴了 ng-alian里面的delon
https://github.com/ng-alain/delon
Angular在官网上显示是一个实验性的功能
解释
具体来说就是重写上述的5个方法
主要代码
route-reuse-strategy.ts
export class CustomeRouteReuseStrategy implements RouteReuseStrategy {
constructor(private reuseTabService: ReuseTabService) { }
/** 表示对所有路由允许复用 如果你有路由不想利用可以在这加一些业务逻辑判断 */
public shouldDetach(route: ActivatedRouteSnapshot): boolean {
return this.reuseTabService.shouldDetach(route);
}
/** 当路由离开时会触发。按path作为key存储路由快照&组件当前实例对象 */
public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
return this.reuseTabService.store(route, handle);
}
/** 若 path 在缓存中有的都认为允许还原路由 */
public shouldAttach(route: ActivatedRouteSnapshot): boolean {
return this.reuseTabService.shouldAttach(route);
}
/** 从缓存中获取快照,若无则返回null */
public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
return this.reuseTabService.retrieve(route);
}
/** 进入路由触发,判断是否同一路由 */
public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return this.reuseTabService.shouldReuseRoute(future,curr);
}
}
reuse-tab-service.ts
import { Injectable, OnDestroy, Injector } from '@angular/core';
import { ActivatedRouteSnapshot, ActivatedRoute } from '@angular/router';
import { ReuseTabCached, ReuseTabNotify } from '../models/reuse-tab';
import { BehaviorSubject, Observable } from 'rxjs';
import { filter } from 'rxjs/operators';
@Injectable()
export class ReuseTabService implements OnDestroy {
private _cachedChange: BehaviorSubject<ReuseTabNotify> = new BehaviorSubject<ReuseTabNotify>(null);
/**缓存路由列表 */
private _reuseTabCachedList: ReuseTabCached[] = [];
private _titleCached: { [url: string]: string } = {};
private removeUrlBuffer: string;
/** 当前路由地址 */
get curUrl() {
return this.getUrl(this.injector.get(ActivatedRoute).snapshot);
}
/** 获取已缓存的路由 */
get reuseTabList(): ReuseTabCached[] {
return this._reuseTabCachedList;
}
/** 获取当前缓存的路由总数 */
get count() {
return this._reuseTabCachedList.length;
}
/** 订阅缓存变更通知 */
get change(): Observable<ReuseTabNotify> {
return this._cachedChange.asObservable()
.pipe(filter(w => w !== null));
}
constructor(private injector: Injector) { }
/**
* 关闭tab
* @param url 路由url
* @param includeNonCloseable 是否包含强制不可关闭
*/
close(url: string, includeNonCloseable = false): any {
this.removeUrlBuffer = url;
this.remove(url, includeNonCloseable);
this._cachedChange.next({ active: 'close', url, list: this._reuseTabCachedList });
this.di('close tag', url);
return true;
}
/**
* 决定是否允许路由复用,若 `true` 会触发 `store`
*/
shouldDetach(route: ActivatedRouteSnapshot): boolean {
if (this.hasInValidRoute(route)) return false;
this.di('#shouldDetach', this.can(route), this.getUrl(route));
return this.can(route);
}
/**
* 存储快照
*/
store(_snapshot: ActivatedRouteSnapshot, _handle: any) {
// if (this.count >= this._max) this._reuseTabCachedList.shift();
const url = this.getUrl(_snapshot);
const index = this.index(url);
const title = this.getTitle(url, _snapshot);
const item: ReuseTabCached = {
title: title,
closable: true,
url: url,
_snapshot: _snapshot,
_handle: _handle,
};
if (index === -1) {
this._reuseTabCachedList.push(item);
} else {
this._reuseTabCachedList[index] = item;
}
this.removeUrlBuffer = null;
this.di('#store', index === -1 ? '[new]' : '[override]', url);
// if (_handle && _handle.componentRef) {
// this.runHook('_onReuseDestroy', url, _handle.componentRef);
// }
this._cachedChange.next({ active: 'add', item, list: this._reuseTabCachedList });
}
/**
* 决定是否允许应用缓存数据
*/
shouldAttach(route: ActivatedRouteSnapshot): boolean {
if (this.hasInValidRoute(route)) return false;
const url = this.getUrl(route);
const data = this.getReuseTabCached(url);
const ret = !!(data && data._handle);
this.di('#shouldAttach', ret, url);
// if (ret && data._handle.componentRef) {
// this.runHook('_onReuseInit', url, data._handle.componentRef);
// }
return ret;
}
/**
* 提取复用数据
*/
retrieve(route: ActivatedRouteSnapshot): {} {
let hasInValidRoute = this.hasInValidRoute(route);
if (hasInValidRoute) return null;
const url = this.getUrl(route);
const data = this.getReuseTabCached(url);
const ret = (data && data._handle) || null;
this.di('#retrieve', url, ret);
return ret;
}
/**
* 决定是否应该进行复用路由处理
*/
shouldReuseRoute(
future: ActivatedRouteSnapshot,
curr: ActivatedRouteSnapshot,
): boolean {
let ret = future.routeConfig === curr.routeConfig;
if (!ret) return false;
const path = ((future.routeConfig && future.routeConfig.path) ||
'') as string;
if (path.length > 0 && ~path.indexOf(':')) {
const futureUrl = this.getUrl(future);
const currUrl = this.getUrl(curr);
ret = futureUrl === currUrl;
}
this.di('=====================');
this.di('#shouldReuseRoute', ret, `${this.getUrl(curr)}=>${this.getUrl(future)}`, future, curr);
return ret;
}
/** 获取指定路径缓存所在位置,`-1` 表示无缓存 */
index(url: string): number {
return this._reuseTabCachedList.findIndex(w => w.url === url);
}
/** 获取指定路径缓存是否存在 */
exists(url: string): boolean {
return this.index(url) !== -1;
}
/** 获取指定路径缓存 */
getReuseTabCached(url: string): ReuseTabCached {
return url ? this._reuseTabCachedList.find(w => w.url === url) || null : null;
}
/**
* 获取标题
* @param url 指定URL
* @param route 指定路由快照
*/
getTitle(url: string, route?: ActivatedRouteSnapshot): string {
if (this._titleCached[url]) return this._titleCached[url];
if (route && route.data && route.data.title)
return route.data.title;
}
/**
* 清除标题缓存
*/
clearTitleCached() {
this._titleCached = {};
}
getTruthRoute(route: ActivatedRouteSnapshot) {
let next = route;
while (next.firstChild) next = next.firstChild;
return next;
}
/**
* 根据快照获取URL地址
*/
getUrl(route: ActivatedRouteSnapshot): string {
let next = this.getTruthRoute(route);
const segments = [];
while (next) {
segments.push(next.url.join('/'));
next = next.parent;
}
const url =
'/' +
segments
.filter(i => i)
.reverse()
.join('/');
return url;
}
/**
* 运行生命周期钩子
* @param method
* @param url
* @param comp
*/
private runHook(method: string, url: string, comp: any) {
if (comp.instance && typeof comp.instance[method] === 'function')
comp.instance[method]();
}
/**
* 组件销毁
* @param _handle
*/
private destroy(_handle: any) {
if (_handle && _handle.componentRef && _handle.componentRef.destroy)
_handle.componentRef.destroy();
}
/**
* 移除url
* @param url
* @param includeNonCloseable
*/
private remove(url: string | number, includeNonCloseable: boolean): boolean {
const idx = typeof url === 'string' ? this.index(url) : url;
const item = idx !== -1 ? this._reuseTabCachedList[idx] : null;
if (!item || (!includeNonCloseable && !item.closable)) return false;
this.destroy(item._handle);
this._reuseTabCachedList.splice(idx, 1);
delete this._titleCached[url];
return true;
}
/**
* 去掉loadChildren,以及children
* 得到纯路由
* @param route
*/
private hasInValidRoute(route: ActivatedRouteSnapshot) {
return (
!route.routeConfig ||
route.routeConfig.loadChildren ||
route.routeConfig.children
);
}
/**
* 检查快照是否允许被复用
*/
private can(route: ActivatedRouteSnapshot): boolean {
const url = this.getUrl(route);
if (url === this.removeUrlBuffer) return false;
if (route.data && typeof route.data.reuse === 'boolean')
return route.data.reuse;
return true;
}
private di(...args) {
// tslint:disable-next-line:no-console
console.warn(...args);
}
ngOnDestroy(): void {
this._reuseTabCachedList = [];
this._cachedChange.unsubscribe();
}
}
app.module.ts
....
providers: [
ReuseTabService,
{
provide: RouteReuseStrategy,
useClass: CustomeRouteReuseStrategy,
deps: [ReuseTabService]
}
]
.....