首先从别的地方了解一下:
思路就是:特定区域就是一个视图容器,可以通过ViewChild来实现获取和查询,然后使用ComponentFactoryResolver将已声明未实例化的组件解析成为可以动态加载的组件component,再将此component呈现到此前视图容器中。
开始我的表演
动态组件加载文档介绍
指令
step1:在添加组件之前,先要定义一个锚点来告诉Angular要把组件插入到什么位置。
import { Directive,ViewContainerRef } from '@angular/core';
/**
* 添加组件之前定义一个锚点来告诉Angular要把组件插入到什么地方
* tab-host 选择器就是用来标记插入组件(Component)
*/
@Directive({
selector: '[tab-host]'
})
export class TabContainerDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
step2:加载组件,HTML 被直接放在了 @Component 装饰器的 template 属性中。
step3:解析组件
在tab-tpl.component.ts中会接收一个TabItem对象的数组作为输入,它最终来自于创建好的tabset.service.ts中
import { Component, OnInit, Input, ViewChild, Type, ComponentFactoryResolver, AfterViewInit, ReflectiveInjector } from '@angular/core';
import { TabContainerDirective } from './../../directives/tab-container.directive';
import { TabItem } from '../../model/tab-item';
/**
* Component组件模板
* ng-template 元素是动态加载组件的最佳选择,因为它不会渲染任何额外的输出
* tab-host 在这里得到应用
*/
@Component({
selector: 'app-tab-tpl',
template: `
`
})
export class TabTplComponent implements OnInit, AfterViewInit {
currentComponent = null;//当前的组件
@Input() tab: TabItem;//需要动态加载组件
@ViewChild(TabContainerDirective) tabHost: TabContainerDirective;
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
//服务,将一个组件的实例沉陷到另一个组件上。
ngOnInit() {
// console.log('视图 init')
}
ngAfterViewInit() {
// this.loadComponent();
// setTimeout ExpressionChangedAfterItHasBeenCheckedError: 对应这个问题 Has it been created in a change detection hook
setTimeout(() => {
this.loadAndPassParams();
}, 1);
}
/**
* 加载组件,不涉及参数传递
*/
loadComponent() {
console.log(this.tab);
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.tab.component);
let viewContainerRef = this.tabHost.viewContainerRef;
let componentRef = viewContainerRef.createComponent(componentFactory);
}
/**
* 加载组件:使用ReflectiveInjector注入参数
*/
loadAndPassParams() {
if (!this.tab.data) {//如果没有传参,默认给一个空对象
this.tab.data = {};
}
let inputProviders = Object.keys(this.tab.data).map(
(key) => {
return {
provide: key, useValue: this.tab.data[key]
};
});
let resolvedInputs = ReflectiveInjector.resolve(inputProviders);
// We create an injector out of the data we want to pass down and this components injector
let dynamicComponentContainer = this.tabHost.viewContainerRef;
let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, dynamicComponentContainer.parentInjector);
// We create a factory out of the component we want to create
let factory = this.componentFactoryResolver.resolveComponentFactory(this.tab.component);
// We create the component using the factory and the injector
let component = factory.create(injector);
// Represents a component created by a ComponentFactory.
//Provides access to the component instance and related objects, and provides the means of destroying the instance
//表示组件工厂创建的组件。提供对组件实例和相关对象的访问,并提供销毁实例的方法
let comInstance = component.instance;
// component instance
this.tab.comInstance = comInstance;
// We insert the component into the dom container
dynamicComponentContainer.insert(component.hostView);
// We can destroy the old component is we like by calling destroy
if (this.currentComponent) {
this.currentComponent.destroy();
}
this.currentComponent = component;
}
}
TabItem对象
import { Type } from '@angular/core';
export class TabItem {
identity: string;//唯一的id 识别
title?: string;//标题
component?: Type;//动态加载的组件
// 表示组件工厂创建的组件。提供对组件实例和相关对象的访问,并提供销毁实例的方法
// comInstance?: ComponentRef; // 动态组件的实例化对象
comInstance?: any; // 动态组件的实例化对象
unremovable?: boolean;//是否可以删除
icon?: string;//图标
//修改后加入的
data?: any;//需要传递的参数// string number object array
}
ObservableTab对象
import { TabItem } from './tab-item';
export class ObservableTab {
tab: TabItem; //需要被观察的tabItem
handle?: string; // 操作类型
}
tabset.service.ts
主要利用rxjs写新增tab页,删除tab页,以及刷新tab页的方法。
import {
Injectable,
Type
} from '@angular/core';
import {
TabItem
} from '../model/tab-item';
import {
Observable,
of ,
Subject,
identity
} from 'rxjs';
import {
Routes
} from '@angular/router';
// import { ManualComponent } from '../components/manual/manual.component';
import {
ObservableTab
} from '../model/observable-tab.model';
import {
HANDLETYPE
} from '../constants';
//
@Injectable({
providedIn: 'root'
})
export class TabsetService {
private routeList = [];
private tabSubject = new Subject < ObservableTab > ();
tabSourceOb: Observable < ObservableTab > = this.tabSubject.asObservable();
constructor() {}
updateRoute(routes: Routes) {
this.routeList = routes;
}
/**
* 新增或者刷新一个tab 标签页
* @param identity=' manualAdd'
* @param data:any 组件传递的值
*/
addTab(identity: string, data ? : any) {
let tab: TabItem = this.generator(identity);
if (data) {
tab.data = data;
}
let observer = new ObservableTab();
observer.tab = tab;
observer.handle = HANDLETYPE.addOrRefresh;
this.tabSubject.next(observer);
}
/**
* 删除一个tab 标签页
* @param tab
*/
removeTab(identity: string) {
let tab: TabItem = this.generator(identity);
let observer = {
tab,
handle: HANDLETYPE.remove
};
this.tabSubject.next(observer);
}
refreshTab(identity: string) {
let tab = this.generator(identity);
if (tab.comInstance.refresh) {
tab.comInstance.refresh()
}
}
/**
* 根据标题返回具体的组件
* @param identity 唯一标识 'manualAdd'
*/
public generator(identity): TabItem {
return this.routeList.find(e => e.identity == identity)
}
/**
* 获取当前tab 标签页的index
* @param tab 通过唯一标识identity找到在数组中的索引
*/
public getIndexByIdentity(identity: string, tabList: Array < TabItem > ): number {
return tabList.findIndex((item) => item.identity == identity);
}
}
export const ROUTELIST: Array = [
{
identity: 'manualList', title: '手册管理', component: ManualComponent, unremovable: true
},
{
identity: 'manualAdd', title: '新增手册', component: ManualHandleComponent
},
{
identity: 'manualEdit', title: '编辑手册', component: ManualHandleComponent
},
{
identity: 'manualInfo', title: '手册详情', component: ManualInfoComponent
},
{
identity: 'productList', title: '产品列表', component: ProductComponent
},
{
identity: 'productAdd', title: '产品入库', component: ProductHandleComponent
},
{
identity: 'productInfo', title: '产品详细', component: ProductInfoComponent
},
{
identity: 'productEdit', title: '入库编辑', component: ProductHandleComponent
},
{
identity: 'qrCode',
title: '二维码标签',
component: Product1Component
},
{
identity: 'productList',
title: '产品列表',
component: ProductComponent
}
]
tabset.component.ts
和tabset页面交互
import {
TabItem
} from '../model/tab-item';
import {
TabsetService
} from './tabset.service';
import {
identity,
Subscription,
Observable
} from 'rxjs';
import {
HttpClient
} from '@angular/common/http';
import {
Component,
OnInit,
OnDestroy,
Injectable
} from '@angular/core';
import * as _ from 'lodash';
import {
ObservableTab
} from '../model/observable-tab.model';
import {
HANDLETYPE
} from '../constants';
@Component({
selector: 'app-tabset',
templateUrl: './tabset.component.html',
styleUrls: ['./tabset.component.less']
})
export class TabsetComponent implements OnInit, OnDestroy {
currentIndex: number = 0;
tabSubscription: Subscription; //主题订阅
tabList: Array < TabItem > = new Array();
constructor(private tabsetService: TabsetService) {
this.tabSubscription = this.tabsetService.tabSourceOb.subscribe((observer: ObservableTab) => {
this.handleTab(observer)
})
}
ngOnInit() {
let tab: TabItem = this.tabsetService.generator('manualList'); //初始化显示的tab并加入到tabList数组中
this.tabList.push(tab);
}
private handleTab(observer: ObservableTab) {
if (observer.handle == HANDLETYPE.remove) { // remove
this.removeTab(observer.tab);
} else if (observer.handle == HANDLETYPE.addOrRefresh) { // 新增或者刷新页面
this.addOrRefresh(observer.tab);
}
}
private removeTab(tab: TabItem) {
if (!tab.unremovable) {
let idx = this.tabsetService.getIndexByIdentity(tab.identity, this.tabList);
this.tabList.splice(idx, 1);
}
}
/**
* 1、没有此tab 时,tablist.push 新增
* 2、已经存在此tab 时,刷新这个tab,调用这个tab component instance 的 refresh 方法
* @param tab
*/
private addOrRefresh(tab: TabItem) {
let idx = this.tabsetService.getIndexByIdentity(tab.identity, this.tabList);
if (idx == -1) { // 不存在
this.tabList.push(tab); // 新增
this.currentIndex = this.tabsetService.getIndexByIdentity(tab.identity, this.tabList);
} else { // 存在
// if (this.currentIndex != idx){
// 重新绑定页面的currentIndex
this.currentIndex = idx;
if (tab.comInstance.refresh) { // 刷新页面
tab.comInstance.refresh(tab.data); // tab 的component instance 去执行页面的 refresh 方法
}
}
}
/**
* tab发生改变
* @param
*/
nzSelectChange(event) {
this.currentIndex = event.index;
}
/**
* 关闭选中的tab
*/
onTabClose(tab ? : any) {
this.removeTab(tab);
}
//点击手动刷新
refresh() {
this.tabList[this.currentIndex].comInstance.refresh();
}
/**
* 关闭其他没有选中的所有tabs
*/
closeOthers() {
let identity = this.tabList[this.currentIndex].identity;
// this.tabList = this.tabList.filter(item => item.identity == identity || item.unremovable);filter不返回原数组。
this.tabList.forEach((ele, index) => {
if (ele.identity != identity && !ele.unremovable) {
this.tabList.splice(index, 1)
}
})
}
closeAll() { //关闭所有tabs
// this.tabList = [this.tabList[0]];
// this.tabList = this.tabList.filter((item,index) => index == 0);
this.tabList.splice(1, (this.tabList.length - 1)) //开始删除的位置,删除的个数;
}
/**
* 添加Component
* @param value 判断哪个应该添加哪个组件
*/
ngOnDestroy(): void {
//取消订阅
this.tabSubscription.unsubscribe();
}
}
tabset.compoment.html
{{tab.title}}
- 刷新当前
- 关闭其他
- 关闭所有