Angular2:使用NG-ZORRO的tabs結(jié)合路由復(fù)用策略實(shí)現(xiàn)動(dòng)態(tài)tab

1.需求,使用路由懶加載的方式實(shí)現(xiàn)動(dòng)態(tài)tab頁(yè),點(diǎn)擊左側(cè)菜單右側(cè)新建一個(gè)tab,

2.新建一個(gè)項(xiàng)目: $ ng new angular-tab

按照ng-zorro官網(wǎng)的步驟導(dǎo)入 ng-zorro

  • 安裝:$ npm install ng-zorro-antd --save
  • 在app.module.ts里導(dǎo)入
    image.png
  • 在.angular-cli.json里導(dǎo)入樣式
    image.png

3.新建兩個(gè)組件,header岭洲,sidebar

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

-header.component.css

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

header組件比較簡(jiǎn)單,就是一個(gè)div設(shè)定了高度

  • 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>系統(tǒng)管理</span>
      <ul>
        <li nz-menu-item>頁(yè)面1</li>
        <li nz-menu-item>頁(yè)面2</li>
        <li nz-menu-item>頁(yè)面3</li>
      </ul>
    </li>
  </ul>
</div>
  • sidebar.component.css, 浮動(dòng)一下坎匿,不然右邊內(nèi)容上不來(lái)
.sidebar{
  float: left;
  width: 240px;
}

siderbar里用到了ng-zorro組件庫(kù)里的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里添加兩個(gè)組件

現(xiàn)在頁(yè)面的效果

image.png

4.在編寫tab之前盾剩,先添加幾個(gè)tab要用到的頁(yè)面

因?yàn)槁酚蓱屑虞d的方式是加載的模塊,所以文件結(jié)構(gòu)是這樣的


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 {
}

說(shuō)明一下:

  • page1.module.ts 因?yàn)槁酚蓱屑虞d是加載的模塊替蔬,所以這個(gè)是給路由懶加載使用的告私,其中聲明了兩個(gè)組件,Page1Component和ContentComponent承桥,其中Page1Component是路由進(jìn)來(lái)顯示的組件驻粟,具體看下文的page1-route.module.ts文件說(shuō)明
  • 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: '', // 當(dāng)訪問(wèn) /page1的時(shí)候顯示Page1Component組件
    component: Page1Component
  }
]
@NgModule({
  imports: [
    RouterModule.forChild(ROUTES)
  ],
  exports: [
    RouterModule
  ]
})
export class Page1RouteModule {
}

說(shuō)明一下:
可以看到Page1RouteModule里設(shè)置了路由,當(dāng)訪問(wèn)/page1 這個(gè)url快毛,會(huì)加載Page1Component組件到頁(yè)面上

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

以同樣的目錄結(jié)構(gòu)建立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: [ // 因?yàn)槭歉酚筛裥幔允褂胒orRoot
    RouterModule.forRoot( ROUTES )
  ],
  exports: [
    RouterModule
  ]
})
export class AppRouterModule {
}

說(shuō)明一下:前面提到的page1.module.ts在這里派上了用場(chǎng)番挺,路由懶加載的方式聲明路由


image.png

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

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

啟動(dòng)項(xiàng)目訪問(wèn) http://localhost:4200/page1

image.png

一唠帝、實(shí)現(xiàn) RouteReuseStrategy 接口自定義一個(gè)路由復(fù)用策略

  • 在service目錄下新建SimpleReuseStrategy.ts文件
import {RouteReuseStrategy, DefaultUrlSerializer, ActivatedRouteSnapshot, DetachedRouteHandle} from '@angular/router';

/**
 * 路由復(fù)用策略
 */
export class SimpleReuseStrategy implements RouteReuseStrategy {

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

  /** 表示對(duì)所有路由允許復(fù)用 如果你有路由不想利用可以在這加一些業(yè)務(wù)邏輯判斷 */
  public shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return true;
  }

  /** 當(dāng)路由離開時(shí)會(huì)觸發(fā)。按path作為key存儲(chǔ)路由快照&組件當(dāng)前實(shí)例對(duì)象 */
  public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    if (SimpleReuseStrategy.waitDelete && SimpleReuseStrategy.waitDelete === this.getRouteUrl(route)) {
      // 如果待刪除是當(dāng)前路由則不存儲(chǔ)快照
      SimpleReuseStrategy.waitDelete = null;
      return;
    }
    SimpleReuseStrategy.handlers[this.getRouteUrl(route)] = handle;
  }

  /** 若 path 在緩存中有的都認(rèn)為允許還原路由 */
  public shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return !!SimpleReuseStrategy.handlers[this.getRouteUrl(route)];
  }

  /** 從緩存中獲取快照玄柏,若無(wú)則返回nul */
  public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    if (!route.routeConfig) {
      return null;
    }

    return SimpleReuseStrategy.handlers[this.getRouteUrl(route)];
  }

  /** 進(jìn)入路由觸發(fā)襟衰,判斷是否同一路由 */
  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;
    }
  }
}

二、策略注冊(cè)到app.module模塊當(dāng)中:

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 { }

四粪摘、新建一個(gè)tab組件并且注冊(cè)到app.module中瀑晒,

image.png
  • tab.component.html,tab頁(yè)的具體使用參照ng-zorro官網(wǎng)徘意,這里拷貝了一段官網(wǎng)的示例
<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>

現(xiàn)在頁(yè)面的樣子


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 = [];
  // 當(dāng)前選擇的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的標(biāo)題
        const menu = {...event};
        menu.url = this.router.url
        const url = menu.url;
        this.titleService.setTitle(menu.title); // 設(shè)置網(wǎng)頁(yè)標(biāo)題
        const exitMenu = this.menuList.find(info => info.url === url);
        if (!exitMenu) {// 如果不存在那么不添加,
          this.menuList.push(menu);
        }
        this.currentIndex = this.menuList.findIndex(p => p.url === url);
      });
  }

  // 關(guān)閉選項(xiàng)標(biāo)簽
  closeUrl(url: string) {
    // 當(dāng)前關(guān)閉的是第幾個(gè)路由
    const index = this.menuList.findIndex(p => p.url === url);
    // 如果只有一個(gè)不可以關(guān)閉
    if (this.menuList.length === 1) {
      return;
    }
    this.menuList.splice(index, 1);
    // 刪除復(fù)用
    // delete SimpleReuseStrategy.handlers[module];
    SimpleReuseStrategy.deleteRouteSnapshot(url)
    // 如果當(dāng)前刪除的對(duì)象是當(dāng)前選中的椎咧,那么需要跳轉(zhuǎn)
    if (this.currentIndex === index) {
      // 顯示上一個(gè)選中
      let menu = this.menuList[index - 1];
      if (!menu) {// 如果上一個(gè)沒(méi)有下一個(gè)選中
        menu = this.menuList[index];
      }
      // 跳轉(zhuǎn)路由
      this.router.navigate([menu.url]);    }
  }
  /**
   * tab發(fā)生改變
   */
  nzSelectChange($event) {
    this.currentIndex = $event.index;
    const menu = this.menuList[this.currentIndex];
    // 跳轉(zhuǎn)路由
    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">
  <!--路由的內(nèi)容會(huì)被顯示在這里-->
  <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:' 頁(yè)面1',
      isRemove: true
    }
  },
  {
    path: 'page2',
    loadChildren: './pages/page2/page2.module#Page2Module',
    data: {
      title: '頁(yè)面2',
      isRemove: true
    }
  },
  {
    path: 'page3',
    loadChildren: './pages/page3/page3.module#Page3Module',
    data: {
      title: '頁(yè)面2',
      isRemove: true
    }
  }
]
@NgModule({
  imports: [ // 因?yàn)槭歉酚删料辏允褂胒orRoot
    RouterModule.forRoot( ROUTES )
  ],
  exports: [
    RouterModule
  ]
})
export class AppRouterModule {
}

六、現(xiàn)在編寫sidebar頁(yè)面勤讽,和tab聯(lián)動(dòng)起來(lái)

  • 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>系統(tǒng)管理</span>
      <ul>
        <li nz-menu-item (click)="tabs('page1')">頁(yè)面1</li>
        <li nz-menu-item (click)="tabs('page2')">頁(yè)面2</li>
        <li nz-menu-item (click)="tabs('page3')">頁(yè)面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]);
  }
}

現(xiàn)在頁(yè)面

image.png

參考文章

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

NG-ZORRO官網(wǎng)

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

項(xiàng)目地址

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蟋座,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子脚牍,更是在濱河造成了極大的恐慌向臀,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诸狭,死亡現(xiàn)場(chǎng)離奇詭異券膀,居然都是意外死亡君纫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門芹彬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)庵芭,“玉大人,你說(shuō)我怎么就攤上這事雀监∷海” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵会前,是天一觀的道長(zhǎng)好乐。 經(jīng)常有香客問(wèn)我,道長(zhǎng)瓦宜,這世上最難降的妖魔是什么蔚万? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮临庇,結(jié)果婚禮上反璃,老公的妹妹穿的比我還像新娘。我一直安慰自己假夺,他們只是感情好淮蜈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著已卷,像睡著了一般梧田。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上侧蘸,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天裁眯,我揣著相機(jī)與錄音,去河邊找鬼讳癌。 笑死穿稳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晌坤。 我是一名探鬼主播逢艘,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泡仗!你這毒婦竟也來(lái)了埋虹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤娩怎,失蹤者是張志新(化名)和其女友劉穎搔课,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爬泥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年柬讨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袍啡。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡踩官,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出境输,到底是詐尸還是另有隱情蔗牡,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布嗅剖,位于F島的核電站辩越,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏信粮。R本人自食惡果不足惜黔攒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望强缘。 院中可真熱鬧督惰,春花似錦、人聲如沸旅掂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辞友。三九已至栅哀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間称龙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工戳晌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鲫尊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓沦偎,卻偏偏與公主長(zhǎng)得像疫向,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子豪嚎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容