一、啥是动态组件加载

在开发 Angular 应用的时候,咱们有时候得在程序运行的时候创建组件。这就好比你玩游戏,有些道具不是一开始就有的,得在游戏过程中根据情况随时拿出来用。动态组件加载就是这么个道理,能让咱们在运行时把组件创建出来,灵活地用在不同的场景里。

比如说,你做一个电商网站,用户点击“查看详情”按钮,就得动态地把商品详情组件加载出来,展示商品的详细信息。要是不用动态组件加载,就得把所有商品详情页面都写死在代码里,那可太麻烦了。

二、为啥要用动态组件加载

应用场景

  1. 弹窗和提示框:当用户做了某些操作,像点击删除按钮,就弹出一个确认删除的提示框。这个提示框组件就可以在运行时动态加载。
// Angular 技术栈
// 假设我们有一个 ConfirmDialogComponent 用于确认删除操作
import { Component, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core';
import { ConfirmDialogComponent } from './confirm-dialog.component';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="openConfirmDialog()">删除</button>
    <ng-template #dialogContainer></ng-template>
  `
})
export class AppComponent {
  @ViewChild('dialogContainer', { read: ViewContainerRef }) dialogContainer: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  openConfirmDialog() {
    // 创建 ConfirmDialogComponent 的工厂
    const factory = this.componentFactoryResolver.resolveComponentFactory(ConfirmDialogComponent);
    // 在容器中创建组件实例
    const componentRef = this.dialogContainer.createComponent(factory);
    // 可以传递数据给组件
    componentRef.instance.message = '确定要删除吗?';
  }
}
  1. 动态表单:根据用户的选择,动态生成不同的表单组件。比如用户选择注册个人账号,就加载个人信息表单;选择注册企业账号,就加载企业信息表单。
// Angular 技术栈
import { Component, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core';
import { PersonalFormComponent } from './personal-form.component';
import { BusinessFormComponent } from './business-form.component';

@Component({
  selector: 'app-root',
  template: `
    <select (change)="onFormTypeChange($event)">
      <option value="personal">个人账号</option>
      <option value="business">企业账号</option>
    </select>
    <ng-template #formContainer></ng-template>
  `
})
export class AppComponent {
  @ViewChild('formContainer', { read: ViewContainerRef }) formContainer: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  onFormTypeChange(event: Event) {
    const selectedValue = (event.target as HTMLSelectElement).value;
    this.formContainer.clear(); // 清空之前的组件

    let componentType;
    if (selectedValue === 'personal') {
      componentType = PersonalFormComponent;
    } else if (selectedValue === 'business') {
      componentType = BusinessFormComponent;
    }

    if (componentType) {
      const factory = this.componentFactoryResolver.resolveComponentFactory(componentType);
      this.formContainer.createComponent(factory);
    }
  }
}

技术优缺点

优点:

  • 灵活性强:能根据不同的条件和用户操作,动态地加载合适的组件,让应用更加智能。
  • 代码复用:同一个组件可以在不同的地方动态加载,提高代码的复用率。

缺点:

  • 复杂度高:动态组件加载涉及到很多概念和操作,比如组件工厂、视图容器等,代码编写和维护的难度较大。
  • 性能问题:频繁地创建和销毁组件可能会影响应用的性能。

三、怎么实现动态组件加载

Angular 提供的关键服务和指令

ComponentFactoryResolver

这个服务就像是一个组件工厂的管理者,能根据组件的类型创建出对应的组件工厂。

// Angular 技术栈
import { Component, ComponentFactoryResolver } from '@angular/core';
import { MyComponent } from './my.component';

@Component({
  selector: 'app-root',
  template: ''
})
export class AppComponent {
  constructor(private componentFactoryResolver: ComponentFactoryResolver) {
    // 创建 MyComponent 的工厂
    const factory = this.componentFactoryResolver.resolveComponentFactory(MyComponent);
  }
}

ViewContainerRef

它代表一个视图容器,可以用来动态地添加和移除组件。

// Angular 技术栈
import { Component, ViewChild, ViewContainerRef } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <ng-template #container></ng-template>
  `
})
export class AppComponent {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;
}

具体步骤

  1. 定义组件:先创建要动态加载的组件。
// Angular 技术栈
import { Component } from '@angular/core';

@Component({
  selector: 'app-my-component',
  template: '<p>这是动态加载的组件</p>'
})
export class MyComponent {}
  1. 获取 ViewContainerRef:在需要加载组件的地方,通过 @ViewChild 获取 ViewContainerRef
// Angular 技术栈
import { Component, ViewChild, ViewContainerRef } from '@angular/core';
import { MyComponent } from './my.component';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="loadComponent()">加载组件</button>
    <ng-template #container></ng-template>
  `
})
export class AppComponent {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  loadComponent() {
    const factory = this.componentFactoryResolver.resolveComponentFactory(MyComponent);
    this.container.createComponent(factory);
  }
}
  1. 创建组件工厂并加载组件:使用 ComponentFactoryResolver 创建组件工厂,然后用 ViewContainerRef 创建组件实例。

四、注意事项

模块配置

要确保动态加载的组件在模块的 entryComponents 中注册,不然会报错。

// Angular 技术栈
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { MyComponent } from './my.component';

@NgModule({
  declarations: [
    AppComponent,
    MyComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent],
  entryComponents: [MyComponent] // 注册动态加载的组件
})
export class AppModule {}

内存管理

动态创建的组件要记得及时销毁,避免内存泄漏。可以在组件销毁时调用 ComponentRef.destroy() 方法。

// Angular 技术栈
import { Component, ComponentFactoryResolver, OnDestroy, ViewChild, ViewContainerRef } from '@angular/core';
import { MyComponent } from './my.component';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="loadComponent()">加载组件</button>
    <button (click)="destroyComponent()">销毁组件</button>
    <ng-template #container></ng-template>
  `
})
export class AppComponent implements OnDestroy {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;
  private componentRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  loadComponent() {
    const factory = this.componentFactoryResolver.resolveComponentFactory(MyComponent);
    this.componentRef = this.container.createComponent(factory);
  }

  destroyComponent() {
    if (this.componentRef) {
      this.componentRef.destroy();
    }
  }

  ngOnDestroy() {
    if (this.componentRef) {
      this.componentRef.destroy();
    }
  }
}

五、文章总结

动态组件加载在 Angular 开发中是个很实用的技术,能让应用更加灵活和智能。它适合用在弹窗、动态表单等场景里,但也有复杂度高和性能方面的问题。在实现动态组件加载时,要用到 ComponentFactoryResolverViewContainerRef 这两个关键服务和指令,并且要注意模块配置和内存管理。掌握了动态组件加载,就能让你的 Angular 应用更上一层楼。