• Angular5.x动态加载组件


    Angular5.x动态加载组件

    前言

    在我们利用Angular进行开发的时候,会碰到这一样的一个场景,随着业务的不断扩大,页面里面的组件、modal、popup、越来越多的时候,页面占用的开销就越大,但是很多组件虽然初始化了,但是确只有在点击的时候才会使用到。

    为了解决这种情况,因而考虑用动态创建组件的方式来加载和使用组件。

    1、未优化前的页面组件情况

    这边以项目中碰到的例子举例,场景是用户管理下有一大堆用户操作弹窗组件,分别在users.component.html

    table-items.component.html 下,table-itemsusers下的子组件,用来显示用户数据。

    <!--users.component.html-->
    
    <new-user-popup #newUserPopup></new-user-popup>
    <user-detail-popup #editUserPopup></user-detail-popup>
    <update-superior (onSubmitSuccess)="refreshData()"></update-superior>
    <add-tags-dialog #addTagsDialog></add-tags-dialog>
    <update-role-dialog #updateRoleDialog></update-role-dialog>
    <tags-filter-button #tagsFilterButton></tags-filter-button>
    <goods-purchases-dialog #goodsPurchasesDialog (onSaveEmitter)="setGoodsPurchases($event)"></goods-purchases-dialog>
    <custom-show-column #customColumn></custom-show-column>
    
    <!--table-items.component.html-->
    
    <add-tags-dialog #addTagsDialog></add-tags-dialog>
    <add-groups-dialog #addGroupsDialog></add-groups-dialog>
    <top-up-dialog #topUpDialog></top-up-dialog>
    <give-vip-dialog #giveVipDialog></give-vip-dialog>
    <give-coupon-dialog #giveCouponDialog></give-coupon-dialog>
    <state-update-dialog #stateUpdateDialog (onSubmit)="refreshData()"></state-update-dialog>
    <user-integral-dialog #userIntegralDialog [userList]="tableItemsService.userList"></user-integral-dialog>
    <balance-dialog #balanceDialog (showDialog)="showTopDialog($event)"></balance-dialog>
    <integral-dialog #integralDialog (showDialog)="showIntegralDialog($event)"></integral-dialog>
    <consume-dialog #consumeDialog></consume-dialog>
    <user-all-export-dialog #userAllExportDialog></user-all-export-dialog>
    <update-role-dialog #updateRoleDialog></update-role-dialog>
    <coupon-detail-dialog #couponDetailDialog (showGiveDialog)="showGiveCouponDialog($event)"></coupon-detail-dialog>
    <times-card-dialog #timesCardDialog (showGiveDialog)="showGiveCouponDialog($event)"></times-card-dialog>
    <allot-guide-dialog #allotGuideDialog (onChange)="tableItemsService.getUserList()"></allot-guide-dialog>
    

    由上面数据来看,一个用户管理列表下的组件就达到20几个,当我们用户数据量多的时候,渲染起来导致的卡顿是非常严重的,但是很多组件确并不是一直被使用的。之后在某一些操作之后才会使用到。

    2、优化后的页面组件情况

    可以看到,我们将所有的组件初始化在页面中去除,只留下一个动态容器,用来存放到时候创建的组件位置。

    <!-- 组件容器,用于动态生成组件 -->
    
    <ng-template #componentContainer></ng-template>
    

    1、创建ViewContainerRef,用来存放视图容器。

    import { ViewContainerRef, ViewChild } from '@angular/core';
    
    export class TableItemsComponent {
      
      // 这边需要利用@ViewChild去获取视图容器,这边有两个参数,第一个参数是容器名或者组件名,第二个参数如果不添加就表示直接获取组件或者元素。
      @ViewChild('componentContainer', { read: ViewContainerRef })
      public componentContainer: ViewContainerRef
    }
    

    2、使用ComponentFactoryResolver创建ComponentFactory组件工厂。

    import { ViewContainerRef, ViewChild, ComponentFactoryResolver } from '@angular/core';
    
    export class TableItemsComponent {
    
      // 这边需要利用@ViewChild去获取视图容器,这边有两个参数,第一个参数是容器名或者组件名,第二个参数如果不添加就表示直接获取组件或者元素。
      @ViewChild('componentContainer', { read: ViewContainerRef })
      public componentContainer: ViewContainerRef;
      
      constructor(
          public resolver: ComponentFactoryResolver
      ) {}
      
      // 创建组件方法
      public createComponent() {
        // 调用ComponentFactoryResolver中的resolveComponentFactory函数,这个函数会返回一个ComponentFactory对象,如下一段TS代码所示
    		const factory = this.resolver.resolveComponentFactory(Component);
    	}
    }
    
    // class ComponentFactoryResolver
    export declare abstract class ComponentFactoryResolver {
        static NULL: ComponentFactoryResolver;
        abstract resolveComponentFactory<T>(component: Type<T>): ComponentFactory<T>;
    }
    

    3、利用ViewContainerRef创建组件引用

    import { ViewContainerRef, ViewChild, ComponentFactoryResolver } from '@angular/core';
    
    export class TableItemsComponent {
      
      constructor(
          public resolver: ComponentFactoryResolver
      ) {}
      
      @ViewChild('componentContainer', { read: ViewContainerRef })
      public componentContainer: ViewContainerRef
      
      public createComponent() {
          const factory = this.resolver.resolveComponentFactory(Component);
          // 调用ViewContainerRef中的createComponent方法,这个方法会返回一个组件引用,如下一段TS代码所示
          const componentRef = this.componentContainer.createComponent(factory);
       }
    }
    
    export declare abstract class ViewContainerRef {
      /**
         * Instantiates a single {@link Component} and inserts its Host View into this container at the
         * specified `index`.
         *
         * The component is instantiated using its {@link ComponentFactory} which can be obtained via
         * {@link ComponentFactoryResolver#resolveComponentFactory resolveComponentFactory}.
         *
         * If `index` is not specified, the new View will be inserted as the last View in the container.
         *
         * You can optionally specify the {@link Injector} that will be used as parent for the Component.
         *
         * Returns the {@link ComponentRef} of the Host View created for the newly instantiated Component.
         */
        abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, 	injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;
    }
    

    4、组合起来创建组件实例

    import { ViewContainerRef, ViewChild, ComponentFactoryResolver } from '@angular/core';
    
    // 组件导入
    import { AddTagsDialogComponent } from '../add-tags-dialog/add-tags-dialog.component';
    import { AddGroupsDialogComponent } from '../add-groups-dialog/add-groups-dialog.component';
    import { TopUpDialogComponent } from '../top-up-dialog/top-up-dialog.component';
    import { GiveVipDialogComponent } from '../give-vip-dialog/give-vip-dialog.component';
    import { GiveVipCouponComponent } from '../give-coupon-dialog/give-coupon-dialog.component';
    import { StateUpdateDialogComponent } from '../state-update-dialog/state-update-dialog.component';
    import { UserIntegralDialogComponent } from '../user-integral-dialog/user-integral-dialog.component';
    import { BalanceDialogComponent } from '../balance-dialog/balance-dialog.component';
    import { IntegralDialogComponent } from '../integral-dialog/integral-dialog.component';
    import { ConsumeDialogComponent } from '../consume-dialog/consume-dialog.component';
    import { UserAllExportDialogComponent } from '../user-all-export-dialog/user-all-export-dialog.component';
    import { UpdateRoleDialogComponent } from '../update-role-dialog/update-role-dialog.component';
    import { CouponDetailComponent } from '../coupon-detail-dialog/coupon-detail-dialog.component';
    import { TimesCardDialogComponent } from '../times-card-dialog/times-card-dialog.component';
    import { AllotGuideDialogComponent } from '../allot-guide-dialog/allot-guide-dialog.component';
    import { IncreaseTagsComponent } from '../increase-tags/increase-tags.component';
    
    export class TableItemsComponent {
      
      @ViewChild('componentContainer', { read: ViewContainerRef })
      public componentContainer: ViewContainerRef
      
      constructor(
    		public resolver: ComponentFactoryResolver
      ) {}
      
      // 以下只是个人的一些实现,并不算太好,这边参考就好,主要提供一个思路。
      
      /**
       * @desc 创建组件
       * @param {string} type - 组件名
       */
      public createComponent(type: string): any {
        // 清空容器
        this.componentContainer.clear();
        const COMPONENT_OBJ = {
          addTagsDialog: {
            instance: AddTagsDialogComponent,
            input: [],
            output: []
          },
          addGroupsDialog: {
            instance: AddGroupsDialogComponent,
            input: [],
            output: []
          },
          topUpDialog: {
            instance: TopUpDialogComponent,
            input: [],
            output: []
          },
          giveVipDialog: {
            instance: GiveVipDialogComponent,
            input: [],
            output: []
          },
          giveCouponDialog: {
            instance: GiveVipCouponComponent,
            input: [],
            output: []
          },
          stateUpdateDialog: {
            instance: StateUpdateDialogComponent,
            input: [],
            output: [{
              origin: 'onSubmit',
              callFn: this.refreshData
            }]
          },
          userIntegralDialog: {
            instance: UserIntegralDialogComponent,
            input: [{
              origin: 'userList',
              to: this.tableItemsService.userList
            }],
            output: []
          },
          balanceDialog: {
            instance: BalanceDialogComponent,
            input: [],
            output: [{
              origin: 'showDialog',
              callFn: this.showTopDialog
            }]
          },
          integralDialog: {
            instance: IntegralDialogComponent,
            input: [],
            output: [{
              origin: 'showDialog',
              callFn: this.showIntegralDialog
            }]
          },
          consumeDialog: {
            instance: ConsumeDialogComponent,
            input: [],
            output: []
          },
          userAllExportDialog: {
            instance: UserAllExportDialogComponent,
            input: [],
            output: []
          },
          updateRoleDialog: {
            instance: UpdateRoleDialogComponent,
            input: [],
            output: []
          },
          couponDetailDialog: {
            instance: CouponDetailComponent,
            input: [],
            output: [{
              origin: 'showGiveDialog',
              callFn: this.showGiveCouponDialog
            }]
          },
          timesCardDialog: {
            instance: TimesCardDialogComponent,
            input: [],
            output: [{
              origin: 'showGiveDialog',
              callFn: this.showGiveCouponDialog
            }]
          },
          allotGuideDialog: {
            instance: AllotGuideDialogComponent,
            input: [],
            output: [{
              origin: 'onChange',
              callFn: this.tableItemsService.getUserList
            }]
          },
          increaseTags: {
            instance: IncreaseTagsComponent,
            input: [],
            output: [{
              origin: 'onSaveSuccess',
              callFn: this.refreshData
            }]
          }
        }[type];
    
        const factory = this.resolver.resolveComponentFactory(COMPONENT_OBJ.instance);
        const componentRef = this.componentContainer.createComponent(factory);
    
        // @Input 输入属性处理
        if (COMPONENT_OBJ['input'].length) {
          COMPONENT_OBJ['input'].forEach((item: any) => {
            componentRef.instance[item.origin] = item.to;
          });
        }
        // @Output 输出方法处理
        if (COMPONENT_OBJ['output'].length) {
          COMPONENT_OBJ['output'].forEach((item: any) => {
            componentRef.instance[item.origin].subscribe(($event: any) => {
              item.callFn.bind(this)($event); // bind解决this指向问题
            });
          });
        }
        // 返回组件实例
        return componentRef.instance;
      }
    }
    
    

    5、NgModule添加entryComponents

    @NgModule({
      entryComponents: [
        NewUserPopupComponent,
        UserDetailPopupComponent,
        UpdateSuperiorComponent,
        GoodsPurchasesDialogComponent,
        CustomShowColumnComponent,
        TagsManagementComponent,
        TagsChooseDialogComponent,
        AddTagsDialogComponent,
        AddGroupsDialogComponent,
        TopUpDialogComponent,
        GiveVipDialogComponent,
        GiveVipCouponComponent,
        StateUpdateDialogComponent,
        UserIntegralDialogComponent,
        BalanceDialogComponent,
        IntegralDialogComponent,
        ConsumeDialogComponent,
        UserAllExportDialogComponent,
        UpdateRoleDialogComponent,
        CouponDetailComponent,
        TimesCardDialogComponent,
        AllotGuideDialogComponent,
        IncreaseTagsComponent
      ]
    })
    

    6、创建并且动态生成组件再调用,减少页面开销

    <!--调用createComponent函数返回函数实例后调用其pop方法用来创建并且展示组件-->
    <li nz-menu-item (click)="createComponent('giveCouponDialog').pop(true)">
    	<span>赠送优惠券</span>
    </li>
    

    3、总结

    1. 通过ViewContainerRef创建容器视图。
    2. 使用ComponentFactoryResolver创建ComponentFactory组件工厂。
    3. 利用ViewContainerRef.createComponent创建组件引用,通过instance引用组件实例。

    通过动态组件组件,在视图容器中每次只存在一个组件,每次点击生成组件的时候才在容器里面插入组件,并不是一开始就把所有的组件渲染之后等着调用。这样大大的提升了页面加载的速度和使用的性能。

    但是要注意的是,通过这样方式动态生成的组件,Input是不会随着OnChanges监听输入属性和的生命周期去检测输入属性变化的,因此在使用这种动态组件的时候,要考虑组件是否有经常变动的Input,在变动的时候要手动去更新里面的值。

  • 相关阅读:
    Leetcode 650
    Leetcode 292
    Leetcode 162
    Leetcode 600
    Leetcode 1894
    知识库
    Win2012R2(英文版)开放远程用户登录数量限制的设置
    Win2012R2(英文版)多账号登录,报错:Select a user to disconnect so that you can sign in的处理
    webstorm修改默认浏览器方法
    处理Chrome等浏览器无法上网,但微信能正常使用问题
  • 原文地址:https://www.cnblogs.com/chenfengami/p/12872461.html
Copyright © 2020-2023  润新知