使用angular路由复用策略实现标签栏效果

前段时间在后台管理端要实现的一个需求是做个类似浏览器中的标签栏。方便用户在不同报表中查看和比对。
查了一些方法,可以通过angular的动态组件方式实现和路由复用的方式实现。

动态组件大体上就是把每个打开的页放入到componentFactory中,然后判断当前应该显示哪个页就好了。

简化的例子,请点击这里。

路由复用

显然这种方式是更合理的,首先实现RouteReuseStrategy中的方法,判断各路由是否使用复用、存储和删除。

app.module.ts

1
2
3
providers: [
  {provide: RouteReuseStrategy, useClass: SimpleReuseStrategy },
  ...

simpleReuseStrategy.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
    export class SimpleReuseStrategy implements RouteReuseStrategy {

public static handlers: { [key: string]: DetachedRouteHandle } = {};

private static waitDelete: string;

public static deleteRouteSnapshot(name: string): void {
if (SimpleReuseStrategy.handlers[name]) {
delete SimpleReuseStrategy.handlers[name];
} else {
SimpleReuseStrategy.waitDelete = name;
}
}

public static getRouteUrl(route: ActivatedRouteSnapshot) {
return route['_routerState'].url.replace(/\/|\?|&|=/g, '_');
}

/** 表示对所有路由允许复用 如果你有路由不想利用可以在这加一些业务逻辑判断 */
public shouldDetach(route: ActivatedRouteSnapshot): boolean {
if (!route.routeConfig || route.routeConfig.loadChildren ||!route.routeConfig.data
    || (route.routeConfig.data&&!route.routeConfig.data.useCache)) {
return false;
}
return true;
}

/** 当路由离开时会触发。按url作为key存储路由快照&组件当前实例对象 */
public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
const routeKey = SimpleReuseStrategy.getRouteUrl(route);
if (SimpleReuseStrategy.waitDelete && SimpleReuseStrategy.waitDelete === routeKey) {
// 如果待删除是当前路由则不存储快照
SimpleReuseStrategy.waitDelete = null;
return;
}
SimpleReuseStrategy.handlers[routeKey] = handle;
}

/** 若 url 在缓存中有的都认为允许还原路由 */
public shouldAttach(route: ActivatedRouteSnapshot): boolean {
let isShow = route.routeConfig.data&&route.routeConfig.data.useCache;
return !!SimpleReuseStrategy.handlers[SimpleReuseStrategy.getRouteUrl(route)]&&isShow;
}

/** 从缓存中获取快照,若无则返回nul */
public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
if (!route.routeConfig) {
return null;
}
// if (route.routeConfig.loadChildren) return null;
const routeKey = SimpleReuseStrategy.getRouteUrl(route);
return SimpleReuseStrategy.handlers[routeKey];
}

/** 进入路由触发,判断是否同一路由 */
public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
const a = future.routeConfig === curr.routeConfig &&
JSON.stringify(future.params) === JSON.stringify(curr.params);

return a;
}

}

这里是我处理路由事件和标签栏的service,需要注意的要订阅的是 ActivationEnd 这路由事件。它会带有当前的路由信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
export class DealWithTabService {
tabList: Array<Tab> = [];
tabsMaxLength = 15;

constructor(public router: Router) {
abp.event.on('Tab.sendTabList', () => {
abp.event.trigger('getTabList', (this.tabList));
});
this.router.events.filter(event => event instanceof ActivationEnd)
.subscribe((event) => {
const snapshot = (event as ActivationEnd).snapshot;

if (snapshot['_routerState'].url && snapshot.routeConfig.data && snapshot.routeConfig.data.useCache) {
let key = snapshot['_routerState'].url.replace(/\/|\?|&|=/g, '_');

const exitMenu = this.tabList.find(info => info.url === key);
if (exitMenu) {
this.tabList.forEach(p => p.isSelect = p.url === key);
return;
}
let title = snapshot.routeConfig.data.title;
this.getTab(snapshot.routeConfig, snapshot.queryParams, snapshot['_routerState'].url, title);
}
});
}


public getTab(routeConfig, queryParams, url, title) {
let isShow = routeConfig.data && routeConfig.data.useCache;
if (!isShow) {
return;
}

let key = url.replace(/\/|\?|&|=/g, '_');

const exitMenu = this.tabList.find(info => {
info.isSelect = false;
return info.url === key;
});
if (exitMenu) {
// 如果存在不添加,当前表示选中
this.tabList.forEach(p => p.isSelect = p.url === key);
return;
}
let title1 = (queryParams && queryParams.title) ? '[' + queryParams.title + ']' : '';
const tab = {
title: title + title1,
module: routeConfig.data.module,
queryParams: queryParams, // 因为我使用了queryParams传参,不建议,我的感觉,处理起来麻烦很多
url: key,
isSelect: true
};

this.tabList.push(tab);

this.sliceTabs();
}

public deleteTabCache(tab: Tab) {
// 当前关闭的是第几个路由
const index = this.tabList.findIndex(p => p.url === tab.url);
// 如果只有一个不可以关闭
if (this.tabList.length === 1) {
return;
}

if (tab.isSelect) {
// 显示上一个选中
let menu = this.tabList[index - 1];
if (!menu) { // 如果上一个没有下一个选中
menu = this.tabList[index + 1];
}
// console.log(menu);
// console.log(this.menuList);
this.tabList.forEach(p => p.isSelect = p.url === menu.url);

// 显示当前路由信息
this.routerNavigate(menu);
}
this.tabList = this.tabList.filter(p => p.url != tab.url);
// 删除复用
SimpleReuseStrategy.deleteRouteSnapshot(tab.url);
this.sliceTabs();
}


   ...
}