You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 8迁移至15:Ivy下Runtime动态组件创建方案咨询

Angular 15 + Ivy 下实现Runtime动态创建组件(服务器加载未知模板)

可以实现这类需求,Angular 15 虽然默认使用AOT编译器并废弃了旧的Compiler、ComponentFactoryResolver API,但仍支持JIT编译来处理动态模板场景,结合Ivy引擎的新能力可以完成你的需求。

核心实现思路

  • 保留动态创建组件类、模块类的逻辑,移除entryComponents(Ivy已自动处理组件入口)
  • 使用@angular/platform-browser-dynamic提供的compileModuleAndAllComponentsAsync替代废弃的Compiler.compileModuleAndAllComponentsAsync
  • 通过ViewContainerRef.createComponent完成组件实例化与挂载

完整实现示例

import { Component, NgModule, ViewContainerRef, Injector, Type, ComponentRef } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { TagsxModule } from './path/to/your/tagsx.module'; // 替换为你的实际路径
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { compileModuleAndAllComponentsAsync } from '@angular/platform-browser-dynamic';

// 假设你的WordReferenceDto定义
interface WordReferenceDto {
  // 你的DTO字段
}

export class DynamicCardService {
  constructor(
    private injector: Injector,
    private viewContainerRef: ViewContainerRef
  ) {}

  // 动态创建组件类
  private createDynamicComponent(selector: string, template: string): Type<any> {
    @Component({
      selector: selector,
      template: template
    })
    class CustomComponent {
      parent: any = {};
      wordRef: WordReferenceDto;

      clicked: any;
      added: any;
      play: any;

      delegate(action: string, arg: any): void {
        const func = this.parent[action];
        func?.call(this.parent, arg);
      }
    }

    return CustomComponent;
  }

  // 动态创建模块类
  private createDynamicModule(component: Type<any>): Type<any> {
    @NgModule({
      declarations: [component],
      imports: [
        CommonModule,
        IonicModule,
        TagsxModule
      ],
      schemas: [NO_ERRORS_SCHEMA] // 允许模板中出现未声明的元素/属性
    })
    class CustomModule {}

    return CustomModule;
  }

  // 渲染动态组件的入口方法
  async renderDynamicProductCard(template: string, selector: string, parentContext: any): Promise<ComponentRef<any>> {
    // 1. 创建组件与模块类
    const customComponent = this.createDynamicComponent(selector, template);
    const customModule = this.createDynamicModule(customComponent);

    // 2. JIT编译模块与组件
    const moduleCompileResult = await compileModuleAndAllComponentsAsync(customModule);
    const componentFactory = moduleCompileResult.componentFactories.find(f => f.componentType === customComponent);
    
    if (!componentFactory) {
      throw new Error('Failed to compile dynamic component');
    }

    // 3. 创建并挂载组件实例
    const componentRef = this.viewContainerRef.createComponent(
      componentFactory,
      undefined,
      this.injector
    );

    // 4. 传递父上下文(用于事件委托)
    componentRef.instance.parent = parentContext;

    return componentRef;
  }
}

关键配置与注意事项

  1. 启用JIT编译:在angular.json的项目build配置中关闭AOT(因为动态模板必须JIT编译):
"projects": {
  "your-app-name": {
    "architect": {
      "build": {
        "options": {
          "aot": false,
          // 其他原有配置...
        }
      }
    }
  }
}
  1. 性能优化:由于你的模板数量高达20万,建议对编译后的ComponentFactory进行缓存,以避免重复编译相同模板:
// 在服务中添加缓存对象
private factoryCache = new Map<string, any>();

// 在render方法中先检查缓存
async renderDynamicProductCard(template: string, selector: string, parentContext: any): Promise<ComponentRef<any>> {
  const cacheKey = template + selector;
  if (this.factoryCache.has(cacheKey)) {
    const componentFactory = this.factoryCache.get(cacheKey);
    const componentRef = this.viewContainerRef.createComponent(componentFactory, undefined, this.injector);
    componentRef.instance.parent = parentContext;
    return componentRef;
  }

  // 原有编译逻辑...
  // 编译完成后存入缓存
  this.factoryCache.set(cacheKey, componentFactory);
}
  1. 安全防护:服务器返回的HTML模板必须经过XSS过滤,避免恶意脚本注入。虽然Angular会自动转义模板中的内容,但仍需对输入的模板进行严格校验。

  2. Ivy兼容性:上述代码完全基于Angular 15的Ivy引擎实现,无需额外配置兼容选项。

内容的提问来源于stack exchange,提问作者k31

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 12:15:31