zoukankan      html  css  js  c++  java
  • Angular2:使用NG-ZORRO的tabs结合路由复用策略实现动态tab

    1.需求,使用路由懒加载的方式实现动态tab页,点击左侧菜单右侧新建一个tab,

    2.新建一个项目: $ ng new angular-tab

    按照ng-zorro官网的步骤导入 ng-zorro

    • 安装:$ npm install ng-zorro-antd --save
    • 在app.module.ts里导入
       
       
      image.png
    •  

      在.angular-cli.json里导入样式
       
      image.png

    3.新建两个组件,header,sidebar

     
    image.png
    • header.component.html
    <div class="header">
    </div>
    

    -header.component.css

    .header{
      height: 50px;
      width: 100%;
      background: lightskyblue;
    }
    

    header组件比较简单,就是一个div设定了高度

    • sidebar.component.html
    <div class="sidebar">
      <ul nz-menu [nzMode]="'inline'" style="width: 240px;">
        <li nz-submenu>
          <span title><i class="anticon anticon-appstore"></i>系统管理</span>
          <ul>
            <li nz-menu-item>页面1</li>
            <li nz-menu-item>页面2</li>
            <li nz-menu-item>页面3</li>
          </ul>
        </li>
      </ul>
    </div>
    
    • sidebar.component.css, 浮动一下,不然右边内容上不来
    .sidebar{
      float: left;
      width: 240px;
    }
    

    siderbar里用到了ng-zorro组件库里的menu组件

    • app.component.html
    <app-header></app-header>
    <app-sidebar></app-sidebar>
    <div class="content">
      123
    </div>
    
    • app.component.css
    .content{
      margin-left: 240px;
    }
    

    在app.component.html里添加两个组件

    现在页面的效果

     
    image.png

    4.在编写tab之前,先添加几个tab要用到的页面

    因为路由懒加载的方式是加载的模块,所以文件结构是这样的


     
    image.png
    • page1.module.ts
    import {NgModule} from '@angular/core';
    import {Page1Component} from './page1.component';
    import {CommonModule} from '@angular/common';
    import {ContentComponent} from './content/content.component';
    @NgModule({
      imports: [
        CommonModule,
        Page1RouteModule
      ],
      declarations: [
        Page1Component,
        ContentComponent
      ]
    })
    export class Page1Module {
    }
    

    说明一下:

    • page1.module.ts 因为路由懒加载是加载的模块,所以这个是给路由懒加载使用的,其中声明了两个组件,Page1Component和ContentComponent,其中Page1Component是路由进来显示的组件,具体看下文的page1-route.module.ts文件说明
    • page1-route.module.ts
    import {NgModule} from '@angular/core';
    import {RouterModule, Routes} from '@angular/router';
    import {Page1Component} from './page1.component';
    export const ROUTES: Routes = [
      {
        path: '', // 当访问 /page1的时候显示Page1Component组件
        component: Page1Component
      }
    ]
    @NgModule({
      imports: [
        RouterModule.forChild(ROUTES)
      ],
      exports: [
        RouterModule
      ]
    })
    export class Page1RouteModule {
    }
    

    说明一下:
    可以看到Page1RouteModule里设置了路由,当访问/page1 这个url,会加载Page1Component组件到页面上

    • page1.component.html
    <app-page1-content></app-page1-content>
    
    • content.component.html
    <p>
      page1的content组件
    </p>
    

    以同样的目录结构建立page2,page3


     
    image.png

    添加路由

    • 在src目录下建立app-route.module.ts
    import {NgModule} from '@angular/core';
    import {RouterModule, Routes} from '@angular/router';
    export const ROUTES: Routes = [
      {
        path: 'page1',
        loadChildren: './pages/page1/page1.module#Page1Module'
      },
      {
        path: 'page2',
        loadChildren: './pages/page2/page2.module#Page2Module'
      },
      {
        path: 'page3',
        loadChildren: './pages/page3/page3.module#Page3Module'
      }
    ]
    @NgModule({
      imports: [ // 因为是根路由,所以使用forRoot
        RouterModule.forRoot( ROUTES )
      ],
      exports: [
        RouterModule
      ]
    })
    export class AppRouterModule {
    }
    

    说明一下:前面提到的page1.module.ts在这里派上了用场,路由懒加载的方式声明路由


     
    image.png

    将根路由添加到app.module.ts中
    修改app.component.html

    <app-header></app-header>
    <app-sidebar></app-sidebar>
    <div class="content">
      <router-outlet></router-outlet>
    </div>
    

    启动项目访问 http://localhost:4200/page1

     
    image.png

    一、实现 RouteReuseStrategy 接口自定义一个路由复用策略

    • 在service目录下新建SimpleReuseStrategy.ts文件
    import {RouteReuseStrategy, DefaultUrlSerializer, ActivatedRouteSnapshot, DetachedRouteHandle} from '@angular/router';
    
    /**
     * 路由复用策略
     */
    export class SimpleReuseStrategy implements RouteReuseStrategy {
    
      public static handlers: { [key: string]: DetachedRouteHandle } = {};
      private static waitDelete: string;
    
      /** 表示对所有路由允许复用 如果你有路由不想利用可以在这加一些业务逻辑判断 */
      public shouldDetach(route: ActivatedRouteSnapshot): boolean {
        return true;
      }
    
      /** 当路由离开时会触发。按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;
      }
    
      /** 若 path 在缓存中有的都认为允许还原路由 */
      public shouldAttach(route: ActivatedRouteSnapshot): boolean {
        return !!SimpleReuseStrategy.handlers[this.getRouteUrl(route)];
      }
    
      /** 从缓存中获取快照,若无则返回nul */
      public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
        if (!route.routeConfig) {
          return null;
        }
    
        return SimpleReuseStrategy.handlers[this.getRouteUrl(route)];
      }
    
      /** 进入路由触发,判断是否同一路由 */
      public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        return future.routeConfig === curr.routeConfig &&
          JSON.stringify(future.params) === JSON.stringify(curr.params);
      }
    
      private getRouteUrl(route: ActivatedRouteSnapshot) {
        return route['_routerState'].url.replace(///g, '_');
      }
    
      public static deleteRouteSnapshot(url: string): void {
        const key = url.replace(///g, '_');
        if (SimpleReuseStrategy.handlers[key]) {
          delete SimpleReuseStrategy.handlers[key];
        } else {
          SimpleReuseStrategy.waitDelete = key;
        }
      }
    }
    

    二、策略注册到app.module模块当中:

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { AppComponent } from './app.component';
    import {NgZorroAntdModule} from 'ng-zorro-antd';
    import {SidebarComponent} from './layout/sidebar/sidebar.component';
    import {HeaderComponent} from './layout/header/header.component';
    import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
    import {AppRouterModule} from './app-router.module';
    import {SimpleReuseStrategy} from './service/SimpleReuseStrategy';
    import {RouteReuseStrategy} from '@angular/router';
    @NgModule({
      declarations: [
        AppComponent,
        SidebarComponent,
        HeaderComponent
      ],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        AppRouterModule,
        NgZorroAntdModule.forRoot()
      ],
      providers: [
        { provide: RouteReuseStrategy, useClass: SimpleReuseStrategy }
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    

    四、新建一个tab组件并且注册到app.module中,

     
    image.png
    • tab.component.html,tab页的具体使用参照ng-zorro官网,这里拷贝了一段官网的示例
    <nz-tabset [nzType]="'card'" [nzSelectedIndex]="index">
      <nz-tab *ngFor="let tab of tabs" [nzTitle]="titleTemplate">
        <ng-template #titleTemplate>
          <div>{{ tab }}<i class="anticon anticon-close" (click)="closeTab(tab)"></i></div>
        </ng-template>
        Content of {{ tab }}
      </nz-tab>
    </nz-tabset>
    
    • tab.component.ts
    import {Component} from '@angular/core';
    @Component({
      selector: 'app-tab',
      templateUrl: './tab.component.html',
      styleUrls: ['./tab.component.css']
    })
    export class TabComponent {
      index = 0;
      tabs = [ 'Tab 1', 'Tab 2' ];
      closeTab(tab: string): void {
        this.tabs.splice(this.tabs.indexOf(tab), 1);
      }
    }
    
    • app.component.html
    <app-header></app-header>
    <app-sidebar></app-sidebar>
    <div class="content">
      <app-tab></app-tab>
    </div>
    

    现在页面的样子


     
    image.png

    五、编写tab代码

    • tab.component.ts
    import {Component} from '@angular/core';
    import {ActivatedRoute, NavigationEnd, Router} from '@angular/router';
    import {Title} from '@angular/platform-browser';
    import {SimpleReuseStrategy} from '../../service/SimpleReuseStrategy';
    
    import 'rxjs/add/operator/filter';
    import 'rxjs/add/operator/map';
    import 'rxjs/add/operator/mergeMap';
    
    @Component({
      selector: 'app-tab',
      templateUrl: './tab.component.html',
      styleUrls: ['./tab.component.css']
    })
    export class TabComponent {
      // 路由列表
      menuList = [];
      // 当前选择的tab index
      currentIndex = -1;
      constructor(private router: Router,
                  private activatedRoute: ActivatedRoute,
                  private titleService: Title) {
    
        // 路由事件
        this.router.events.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的标题
            const menu = {...event};
            menu.url = this.router.url
            const url = menu.url;
            this.titleService.setTitle(menu.title); // 设置网页标题
            const exitMenu = this.menuList.find(info => info.url === url);
            if (!exitMenu) {// 如果不存在那么不添加,
              this.menuList.push(menu);
            }
            this.currentIndex = this.menuList.findIndex(p => p.url === url);
          });
      }
    
      // 关闭选项标签
      closeUrl(url: string) {
        // 当前关闭的是第几个路由
        const index = this.menuList.findIndex(p => p.url === url);
        // 如果只有一个不可以关闭
        if (this.menuList.length === 1) {
          return;
        }
        this.menuList.splice(index, 1);
        // 删除复用
        // delete SimpleReuseStrategy.handlers[module];
        SimpleReuseStrategy.deleteRouteSnapshot(url)
        // 如果当前删除的对象是当前选中的,那么需要跳转
        if (this.currentIndex === index) {
          // 显示上一个选中
          let menu = this.menuList[index - 1];
          if (!menu) {// 如果上一个没有下一个选中
            menu = this.menuList[index];
          }
          // 跳转路由
          this.router.navigate([menu.url]);    }
      }
      /**
       * tab发生改变
       */
      nzSelectChange($event) {
        this.currentIndex = $event.index;
        const menu = this.menuList[this.currentIndex];
        // 跳转路由
        this.router.navigate([menu.url]);
      }
    
    }
    
    • tab.component.html
    <nz-tabset style="margin-left: -1px;" [nzAnimated]="true"
               [nzSelectedIndex]="currentIndex"
               [nzShowPagination]="true"
               (nzSelectChange)="nzSelectChange($event)"
               [nzType]="'card'">
      <nz-tab *ngFor="let menu of menuList" [nzTitle]="nzTabHeading">
        <ng-template #nzTabHeading>
          <div>
            {{menu.title}}
            <i *ngIf="menu.isRemove" (click)="closeUrl(menu.url)" class="anticon anticon-cross" ></i>
          </div>
        </ng-template>
      </nz-tab>
    </nz-tabset>
    <div class="tab-content">
      <!--路由的内容会被显示在这里-->
      <ng-content></ng-content>
    </div>
    
    • app.component.html
    <app-header></app-header>
    <app-sidebar></app-sidebar>
    <div class="content">
      <app-tab>
        <router-outlet></router-outlet>
      </app-tab>
    </div>
    
    • app-route.module.ts
    import {NgModule} from '@angular/core';
    import {RouterModule, Routes} from '@angular/router';
    export const ROUTES: Routes = [
      {
        path: 'page1',
        loadChildren: './pages/page1/page1.module#Page1Module',
        data: {
          title:' 页面1',
          isRemove: true
        }
      },
      {
        path: 'page2',
        loadChildren: './pages/page2/page2.module#Page2Module',
        data: {
          title: '页面2',
          isRemove: true
        }
      },
      {
        path: 'page3',
        loadChildren: './pages/page3/page3.module#Page3Module',
        data: {
          title: '页面2',
          isRemove: true
        }
      }
    ]
    @NgModule({
      imports: [ // 因为是根路由,所以使用forRoot
        RouterModule.forRoot( ROUTES )
      ],
      exports: [
        RouterModule
      ]
    })
    export class AppRouterModule {
    }
    

    六、现在编写sidebar页面,和tab联动起来

    • sidebar.component.html
    <div class="sidebar">
      <ul nz-menu [nzMode]="'inline'" style="width: 240px;">
        <li nz-submenu>
          <span title><i class="anticon anticon-appstore"></i>系统管理</span>
          <ul>
            <li nz-menu-item (click)="tabs('page1')">页面1</li>
            <li nz-menu-item (click)="tabs('page2')">页面2</li>
            <li nz-menu-item (click)="tabs('page3')">页面3</li>
          </ul>
        </li>
      </ul>
    </div>
    
    • sidebar.component.ts
    import {Component, OnInit} from '@angular/core';
    import {Router} from '@angular/router';
    @Component({
      selector: 'app-sidebar',
      templateUrl: './sidebar.component.html',
      styleUrls: ['./sidebar.component.css']
    })
    export class SidebarComponent implements OnInit {
      constructor(private router: Router) { }
      ngOnInit() {
      }
      /**
       * 路由方式添加tab
       * @param data
       */
      tabs(data) {
        this.router.navigate([data]);
      }
    }
    

    现在页面

     
    image.png

    参考文章

    https://www.cnblogs.com/lovesangel/p/7853364.html
    http://www.cnblogs.com/lslgg/p/7700888.html

    NG-ZORRO官网

    https://ng.ant.design/docs/getting-started/zh

    项目地址

    https://github.com/Ariesssssssss/angular-tab



    作者:Lautumn
    链接:https://www.jianshu.com/p/c8ed7db7dd0f
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 相关阅读:
    C#基础:单例模式与多线程
    C#基础:线程同步
    C#基础:C#中的数据结构
    C#基础:GC中什么是代,一共分几代
    C#基础:Dispose方法和Finalize方法在何时被调用
    C#基础:.NET中GC的运行机制
    C#基础:简述.NET中堆栈和堆的特点和差异
    PLSQL_基础系列05_视图控制WITH CHECK OPTION(案例)
    PLSQL_基础系列04_时间间隔INTERVAL(案例)
    PLSQL_基础系列03_合并操作UNION / UNION ALL / MINUS / INTERSET(案例)
  • 原文地址:https://www.cnblogs.com/telwanggs/p/14744651.html
Copyright © 2011-2022 走看看