Angular2:使用NG-ZORRO的tabs结合路由复用策略实现动态tab
1.需求,使用路由懒加载的方式实现动态tab页,点击左侧菜单右侧新建一个tab,
2.新建一个项目: $ ng new angular-tab
按照ng-zorro官网的步骤导入 ng-zorro
- 安装:$ npm install ng-zorro-antd --save
-
-
3.新建两个组件,header,sidebar
- 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里添加两个组件
现在页面的效果
4.在编写tab之前,先添加几个tab要用到的页面
因为路由懒加载的方式是加载的模块,所以文件结构是这样的
- 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
添加路由
- 在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在这里派上了用场,路由懒加载的方式声明路由
将根路由添加到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
一、实现 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中,
- 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>
现在页面的样子
五、编写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)