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)入
-
在.angular-cli.json里導(dǎo)入樣式
3.新建兩個(gè)組件,header岭洲,sidebar
- 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è)面的效果
4.在編寫tab之前盾剩,先添加幾個(gè)tab要用到的頁(yè)面
因?yàn)槁酚蓱屑虞d的方式是加載的模塊,所以文件結(jié)構(gòu)是這樣的
- 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
添加路由
- 在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)番挺,路由懶加載的方式聲明路由
將根路由添加到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
一唠帝、實(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中瀑晒,
- 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è)面的樣子
五苔悦、編寫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è)面
參考文章
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