angular中的动态组件加载

首先从别的地方了解一下:

  1. ViewChild:属性装饰器,通过它可以获取视图上对应的元素。文档链接
  2. ViewContainerRef:视图容器,可在上面创建、插入、删除组件。文档链接
  3. ComponentFactoryResolver:一个服务,动态加载组件的核心,这个服务可以将一个组件实例呈现到另一个组件视图上。文档链接

思路就是:特定区域就是一个视图容器,可以通过ViewChild来实现获取和查询,然后使用ComponentFactoryResolver将已声明未实例化的组件解析成为可以动态加载的组件component,再将此component呈现到此前视图容器中。

angular中的动态组件加载_第1张图片
开始我的表演
动态组件加载文档介绍
指令
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; // 操作类型
}

angular中的动态组件加载_第2张图片
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
  }
]

angular中的动态组件加载_第3张图片
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}}
  • 刷新当前
  • 关闭其他
  • 关闭所有

你可能感兴趣的:(angular的学习,日常忽悠总结)