Angular7多标签页实现(路由复用+路由懒加载)

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.最终实现效果图如下:
在这里插入图片描述

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值