Angular动态组件加载

动态组件加载是Angular高级特性,允许在运行时动态创建、加载和销毁组件,实现高度灵活的插件化架构。

核心概念:动态组件加载使用ComponentFactoryResolverViewContainerRefComponentRef在运行时创建组件实例。

1. 基础动态组件加载

动态组件演示

点击下方按钮动态加载不同组件

动态组件将显示在这里...

1.1 传统方式(Angular 13之前)

// dynamic-loader.component.ts
import {
  Component,
  ComponentFactoryResolver,
  ViewContainerRef,
  ViewChild,
  AfterViewInit,
  OnDestroy
} from '@angular/core';
import { AlertComponent } from './alert.component';
import { CardComponent } from './card.component';

@Component({
  selector: 'app-dynamic-loader',
  template: `
    <div class="dynamic-loader">
      <div class="controls">
        <button (click)="loadAlert()">加载Alert</button>
        <button (click)="loadCard()">加载Card</button>
        <button (click)="destroyComponent()">销毁组件</button>
      </div>

      <!-- 组件容器 -->
      <ng-container #container></ng-container>
    </div>
  `
})
export class DynamicLoaderComponent implements AfterViewInit, OnDestroy {
  @ViewChild('container', { read: ViewContainerRef }) container!: ViewContainerRef;

  private componentRef: any;
  private components = new Map<string, any>();

  constructor(
    private componentFactoryResolver: ComponentFactoryResolver
  ) {}

  ngAfterViewInit(): void {
    // 预加载组件工厂
    this.preloadComponents();
  }

  private preloadComponents(): void {
    // 注册可用组件
    this.components.set('alert', AlertComponent);
    this.components.set('card', CardComponent);
  }

  loadAlert(): void {
    this.loadComponent('alert', {
      title: '动态Alert',
      message: '这是动态加载的Alert组件',
      type: 'success'
    });
  }

  loadCard(): void {
    this.loadComponent('card', {
      title: '动态Card',
      content: '这是动态加载的Card组件',
      footer: 'Card底部'
    });
  }

  private loadComponent(componentName: string, inputs?: any): void {
    // 清理现有组件
    this.destroyComponent();

    // 获取组件类
    const component = this.components.get(componentName);
    if (!component) {
      console.error(`组件 ${componentName} 未注册`);
      return;
    }

    try {
      // 创建组件工厂
      const componentFactory = this.componentFactoryResolver
        .resolveComponentFactory(component);

      // 创建组件实例
      this.componentRef = this.container
        .createComponent(componentFactory);

      // 设置输入属性
      if (inputs && this.componentRef.instance) {
        Object.keys(inputs).forEach(key => {
          this.componentRef.instance[key] = inputs[key];
        });
      }

      // 监听输出事件
      if (this.componentRef.instance['onClose']) {
        this.componentRef.instance['onClose'].subscribe(() => {
          console.log('组件关闭事件');
          this.destroyComponent();
        });
      }

      console.log(`${componentName}组件加载成功`);
    } catch (error) {
      console.error(`加载组件失败:`, error);
    }
  }

  destroyComponent(): void {
    if (this.componentRef) {
      this.componentRef.destroy();
      this.componentRef = null;
      console.log('组件已销毁');
    }
  }

  ngOnDestroy(): void {
    this.destroyComponent();
  }
}
// alert.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-alert',
  template: `
    <div class="alert" [class]="type">
      <div class="alert-header">
        <h4>{{title}}</h4>
        <button (click)="close()">×</button>
      </div>
      <div class="alert-body">
        {{message}}
      </div>
    </div>
  `,
  styles: [`
    .alert {
      border: 1px solid transparent;
      border-radius: 8px;
      padding: 15px;
      margin: 10px 0;
    }
    .success {
      background-color: #d4edda;
      border-color: #c3e6cb;
      color: #155724;
    }
    .warning {
      background-color: #fff3cd;
      border-color: #ffeaa7;
      color: #856404;
    }
    .error {
      background-color: #f8d7da;
      border-color: #f5c6cb;
      color: #721c24;
    }
    .alert-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
    }
    .alert-header button {
      background: none;
      border: none;
      font-size: 24px;
      cursor: pointer;
      color: inherit;
    }
  `]
})
export class AlertComponent {
  @Input() title = '提示';
  @Input() message = '';
  @Input() type: 'success' | 'warning' | 'error' = 'success';

  @Output() onClose = new EventEmitter<void>();

  close(): void {
    this.onClose.emit();
  }
}

1.2 现代方式(Angular 13+)

// modern-dynamic-loader.component.ts (Angular 13+)
import {
  Component,
  ViewContainerRef,
  ViewChild,
  ComponentRef,
  Type
} from '@angular/core';
import { AlertComponent } from './alert.component';
import { CardComponent } from './card.component';

@Component({
  selector: 'app-modern-dynamic-loader',
  template: `
    <div class="modern-loader">
      <div class="controls">
        <button (click)="loadComponent(AlertComponent, alertConfig)">
          加载Alert
        </button>
        <button (click)="loadComponent(CardComponent, cardConfig)">
          加载Card
        </button>
        <button (click)="clear()">清空</button>
      </div>

      <!-- 组件容器 -->
      <ng-container #container></ng-container>
    </div>
  `
})
export class ModernDynamicLoaderComponent {
  @ViewChild('container', { read: ViewContainerRef }) container!: ViewContainerRef;

  private componentRefs: ComponentRef<any>[] = [];

  // 组件配置
  alertConfig = {
    title: '现代动态Alert',
    message: '使用Angular 13+新API',
    type: 'success' as const
  };

  cardConfig = {
    title: '现代动态Card',
    content: '无需ComponentFactoryResolver',
    footer: 'Card底部'
  };

  async loadComponent(component: Type<any>, inputs?: any): Promise<void> {
    try {
      // 动态导入组件(如果需要懒加载)
      // const { component } = await import('./path/to/component');

      // 创建组件实例
      const componentRef = this.container.createComponent(component);

      // 设置输入属性
      if (inputs && componentRef.instance) {
        Object.assign(componentRef.instance, inputs);

        // 手动触发变更检测(如果需要)
        componentRef.changeDetectorRef.detectChanges();
      }

      // 存储引用以便清理
      this.componentRefs.push(componentRef);

      console.log('组件加载成功');
    } catch (error) {
      console.error('加载组件失败:', error);
    }
  }

  clear(): void {
    this.componentRefs.forEach(ref => ref.destroy());
    this.componentRefs = [];
    this.container.clear();
    console.log('所有组件已销毁');
  }
}

2. 动态组件生命周期

1
组件工厂解析
2
组件实例化
3
属性绑定
4
视图渲染
5
生命周期钩子
6
组件销毁
// lifecycle-aware.component.ts - 支持动态生命周期的组件
import {
  Component,
  Input,
  OnDestroy,
  OnInit,
  OnChanges,
  SimpleChanges,
  EventEmitter,
  Output
} from '@angular/core';

@Component({
  selector: 'app-lifecycle-aware',
  template: `
    <div class="lifecycle-component">
      <h4>生命周期感知组件</h4>
      <p>计数: {{count}}</p>
      <button (click)="increment()">增加</button>
      <button (click)="destroy.emit()">请求销毁</button>
    </div>
  `,
  styles: [`
    .lifecycle-component {
      padding: 20px;
      border: 2px solid #3498db;
      border-radius: 8px;
      background: #e3f2fd;
      margin: 10px 0;
    }
  `]
})
export class LifecycleAwareComponent implements OnInit, OnChanges, OnDestroy {
  @Input() count = 0;
  @Output() destroy = new EventEmitter<void>();
  @Output() countChanged = new EventEmitter<number>();

  private initialized = false;

  constructor() {
    console.log('🚀 构造函数调用');
  }

  ngOnInit(): void {
    console.log('✅ ngOnInit - 组件初始化完成');
    this.initialized = true;
  }

  ngOnChanges(changes: SimpleChanges): void {
    console.log('🔄 ngOnChanges - 输入属性变化:', changes);
  }

  increment(): void {
    this.count++;
    this.countChanged.emit(this.count);
    console.log(`🔢 计数增加: ${this.count}`);
  }

  ngOnDestroy(): void {
    console.log('🗑️ ngOnDestroy - 组件销毁');
    // 清理资源
  }

  // 自定义方法,供动态加载器调用
  public customMethod(): void {
    console.log('🎯 自定义方法被调用');
  }
}

// 在动态加载器中处理生命周期
private async loadWithLifecycle(): Promise<void> {
  const componentRef = this.container.createComponent(LifecycleAwareComponent);

  // 设置输入
  componentRef.instance.count = 10;

  // 监听输出
  componentRef.instance.destroy.subscribe(() => {
    console.log('收到销毁请求');
    componentRef.destroy();
  });

  componentRef.instance.countChanged.subscribe((count: number) => {
    console.log('计数变化:', count);
  });

  // 等待组件初始化
  await new Promise(resolve => setTimeout(resolve, 0));

  // 调用组件方法
  if (componentRef.instance.customMethod) {
    componentRef.instance.customMethod();
  }

  // 手动触发变更检测
  componentRef.changeDetectorRef.detectChanges();
}

3. 动态组件通信

通信方式:动态组件与宿主组件之间可以通过输入属性、输出事件、服务和模板变量进行通信。
// dynamic-communication.component.ts
import {
  Component,
  ViewContainerRef,
  ViewChild,
  ComponentRef,
  Injector,
  Input
} from '@angular/core';
import { DynamicFormComponent } from './dynamic-form.component';
import { CommunicationService } from './communication.service';

@Component({
  selector: 'app-dynamic-communication',
  template: `
    <div class="communication-demo">
      <h3>动态组件通信</h3>

      <div class="host-controls">
        <input [(ngModel)]="formData.name" placeholder="姓名">
        <input [(ngModel)]="formData.email" placeholder="邮箱">
        <button (click)="updateDynamicComponent()">更新动态组件</button>
      </div>

      <!-- 动态组件容器 -->
      <div #container></div>

      <div class="output-log">
        <h4>事件日志</h4>
        <div *ngFor="let log of logs">{{log}}</div>
      </div>
    </div>
  `
})
export class DynamicCommunicationComponent {
  @ViewChild('container', { read: ViewContainerRef }) container!: ViewContainerRef;
  @Input() initialData: any = {};

  private componentRef: ComponentRef<DynamicFormComponent> | null = null;
  private logs: string[] = [];

  formData = {
    name: '',
    email: ''
  };

  constructor(
    private injector: Injector,
    private communicationService: CommunicationService
  ) {}

  ngAfterViewInit(): void {
    this.loadDynamicForm();

    // 通过服务通信
    this.communicationService.data$.subscribe(data => {
      this.logs.push(`服务通信: ${JSON.stringify(data)}`);
    });
  }

  private async loadDynamicForm(): Promise<void> {
    // 创建自定义注入器(如果需要)
    const customInjector = Injector.create({
      parent: this.injector,
      providers: [
        { provide: 'CONFIG', useValue: { theme: 'dark' } }
      ]
    });

    // 加载组件
    const { DynamicFormComponent } = await import('./dynamic-form.component');

    this.componentRef = this.container.createComponent(DynamicFormComponent, {
      injector: customInjector
    });

    // 方法1:通过输入属性传递数据
    this.componentRef.instance.initialData = this.initialData;
    this.componentRef.instance.data = this.formData;

    // 方法2:通过输出事件接收数据
    this.componentRef.instance.submit.subscribe((data: any) => {
      this.logs.push(`表单提交: ${JSON.stringify(data)}`);
      this.handleFormSubmit(data);
    });

    // 方法3:通过服务通信
    this.componentRef.instance.service = this.communicationService;

    // 方法4:通过模板变量(需要在组件中暴露)
    // 使用componentRef.instance直接访问

    // 触发变更检测
    this.componentRef.changeDetectorRef.detectChanges();
  }

  updateDynamicComponent(): void {
    if (this.componentRef) {
      // 更新输入属性
      this.componentRef.instance.data = { ...this.formData };

      // 手动调用组件方法
      if (this.componentRef.instance.update) {
        this.componentRef.instance.update();
      }

      // 触发变更检测
      this.componentRef.changeDetectorRef.detectChanges();
    }
  }

  private handleFormSubmit(data: any): void {
    console.log('处理表单提交:', data);
    // 更新逻辑...
  }
}

4. 插件系统架构

插件系统架构:
┌─────────────────────────────────────────────────┐
│                 宿主应用 (Host App)              │
│  ┌─────────────┐  ┌─────────────┐              │
│  │  插件管理器 │  │ 插件注册中心 │              │
│  └──────┬──────┘  └──────┬──────┘              │
│         │                │                     │
│         └───────┬────────┘                     │
│                 │                              │
│          ┌──────▼──────┐                       │
│          │ 插件加载器  │                       │
│          └──────┬──────┘                       │
│                 │                              │
├─────────────────┼──────────────────────────────┤
│          ┌──────▼──────┐                       │
│          │  插件模块   │                       │
│          │ (懒加载)    │                       │
│          └──────┬──────┘                       │
│                 │                              │
│          ┌──────▼──────┐                       │
│          │  插件组件   │                       │
│          └─────────────┘                       │
└─────────────────────────────────────────────────┘
// plugin-system.service.ts - 插件管理服务
import { Injectable, ComponentFactoryResolver, Injector, Type, ComponentRef } from '@angular/core';
import { PluginRegistry } from './plugin-registry';

export interface PluginConfig {
  id: string;
  name: string;
  version: string;
  component: Type;
  config?: any;
}

@Injectable({
  providedIn: 'root'
})
export class PluginSystemService {
  private plugins = new Map<string, PluginConfig>();
  private loadedPlugins = new Map<string, ComponentRef

5. 实战案例:动态仪表板

// dashboard.component.ts - 动态仪表板
import {
  Component,
  ViewContainerRef,
  ViewChild,
  AfterViewInit,
  ComponentRef
} from '@angular/core';
import { WidgetService, WidgetConfig } from './widget.service';

interface DashboardWidget {
  id: string;
  title: string;
  component: any;
  config: any;
  ref?: ComponentRef;
  position: { x: number, y: number };
  size: { width: number, height: number };
}

@Component({
  selector: 'app-dynamic-dashboard',
  template: `
    <div class="dashboard">
      <div class="dashboard-header">
        <h2>动态仪表板</h2>
        <div class="widget-controls">
          <select [(ngModel)]="selectedWidget">
            <option value="">选择小部件</option>
            <option *ngFor="let widget of availableWidgets" [value]="widget.id">
              {{widget.name}}
            </option>
          </select>
          <button (click)="addWidget()">添加小部件</button>
          <button (click)="saveLayout()">保存布局</button>
          <button (click)="resetLayout()">重置</button>
        </div>
      </div>

      <div class="dashboard-body" #dashboardBody>
        <div class="widget-grid">
          <div *ngFor="let widget of widgets"
               class="widget-container"
               [style.gridColumn]="widget.position.x"
               [style.gridRow]="widget.position.y"
               [style.width]="widget.size.width + 'px'"
               [style.height]="widget.size.height + 'px'">

            <div class="widget-header">
              <span>{{widget.title}}</span>
              <div class="widget-actions">
                <button (click)="refreshWidget(widget.id)">↻</button>
                <button (click)="removeWidget(widget.id)">×</button>
              </div>
            </div>

            <div class="widget-content" #widgetContainer></div>
          </div>
        </div>
      </div>

      <div class="dashboard-footer">
        <span>已加载 {{widgets.length}} 个小部件</span>
      </div>
    </div>
  `,
  styles: [`
    .dashboard {
      display: flex;
      flex-direction: column;
      height: 600px;
      border: 1px solid #dee2e6;
      border-radius: 8px;
      overflow: hidden;
    }

    .dashboard-header {
      background: #2c3e50;
      color: white;
      padding: 15px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .widget-controls {
      display: flex;
      gap: 10px;
      align-items: center;
    }

    .dashboard-body {
      flex: 1;
      padding: 20px;
      overflow: auto;
    }

    .widget-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
      gap: 20px;
    }

    .widget-container {
      border: 1px solid #dee2e6;
      border-radius: 6px;
      overflow: hidden;
      background: white;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }

    .widget-header {
      background: #f8f9fa;
      padding: 10px 15px;
      border-bottom: 1px solid #dee2e6;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .widget-actions {
      display: flex;
      gap: 5px;
    }

    .widget-content {
      padding: 15px;
      height: calc(100% - 45px);
    }
  `]
})
export class DynamicDashboardComponent implements AfterViewInit {
  @ViewChild('dashboardBody', { read: ViewContainerRef }) dashboardBody!: ViewContainerRef;

  widgets: DashboardWidget[] = [];
  availableWidgets: WidgetConfig[] = [];
  selectedWidget = '';

  constructor(private widgetService: WidgetService) {}

  ngAfterViewInit(): void {
    this.loadAvailableWidgets();
    this.loadSavedLayout();
  }

  private loadAvailableWidgets(): void {
    this.availableWidgets = this.widgetService.getAvailableWidgets();
  }

  private loadSavedLayout(): void {
    const savedLayout = localStorage.getItem('dashboard-layout');
    if (savedLayout) {
      this.widgets = JSON.parse(savedLayout);
      this.loadWidgets();
    }
  }

  async addWidget(): Promise<void> {
    if (!this.selectedWidget) return;

    const widgetConfig = this.availableWidgets
      .find(w => w.id === this.selectedWidget);

    if (!widgetConfig) return;

    const widget: DashboardWidget = {
      id: `${widgetConfig.id}-${Date.now()}`,
      title: widgetConfig.name,
      component: widgetConfig.component,
      config: widgetConfig.defaultConfig || {},
      position: { x: 1, y: 1 },
      size: { width: 300, height: 200 }
    };

    this.widgets.push(widget);
    await this.loadWidget(widget);
  }

  private async loadWidgets(): Promise<void> {
    for (const widget of this.widgets) {
      await this.loadWidget(widget);
    }
  }

  private async loadWidget(widget: DashboardWidget): Promise<void> {
    try {
      // 动态加载组件
      const componentRef = this.dashboardBody.createComponent(widget.component);

      // 设置配置
      Object.assign(componentRef.instance, widget.config);

      // 监听组件事件
      if (componentRef.instance.dataUpdate) {
        componentRef.instance.dataUpdate.subscribe((data: any) => {
          this.handleWidgetDataUpdate(widget.id, data);
        });
      }

      // 存储引用
      widget.ref = componentRef;

      // 触发变更检测
      componentRef.changeDetectorRef.detectChanges();

    } catch (error) {
      console.error(`加载小部件失败 ${widget.id}:`, error);
    }
  }

  refreshWidget(widgetId: string): void {
    const widget = this.widgets.find(w => w.id === widgetId);
    if (widget?.ref?.instance?.refresh) {
      widget.ref.instance.refresh();
    }
  }

  removeWidget(widgetId: string): void {
    const index = this.widgets.findIndex(w => w.id === widgetId);
    if (index > -1) {
      const widget = this.widgets[index];

      // 销毁组件
      if (widget.ref) {
        widget.ref.destroy();
      }

      // 从数组中移除
      this.widgets.splice(index, 1);
    }
  }

  saveLayout(): void {
    // 移除组件引用(不能序列化)
    const layoutToSave = this.widgets.map(w => ({
      ...w,
      ref: undefined
    }));

    localStorage.setItem('dashboard-layout', JSON.stringify(layoutToSave));
    alert('布局已保存');
  }

  resetLayout(): void {
    if (confirm('确定要重置仪表板吗?')) {
      // 销毁所有组件
      this.widgets.forEach(widget => {
        if (widget.ref) {
          widget.ref.destroy();
        }
      });

      // 清空数组
      this.widgets = [];
      localStorage.removeItem('dashboard-layout');
    }
  }

  private handleWidgetDataUpdate(widgetId: string, data: any): void {
    console.log(`小部件 ${widgetId} 数据更新:`, data);
    // 处理数据更新逻辑
  }
}

6. 性能优化与最佳实践

性能优化建议:
  • 使用懒加载减少初始包体积
  • 实现组件缓存机制,避免重复创建
  • 使用OnPush变更检测策略
  • 合理管理组件引用,防止内存泄漏
  • 实现虚拟滚动处理大量动态组件
  • 使用Web Workers处理复杂计算
  • 实现组件预加载机制
// component-cache.service.ts - 组件缓存服务
import { Injectable, ComponentFactoryResolver, ComponentRef, Type } from '@angular/core';

interface CacheEntry {
  componentRef: ComponentRef;
  lastUsed: number;
  useCount: number;
}

@Injectable({
  providedIn: 'root'
})
export class ComponentCacheService {
  private cache = new Map<string, CacheEntry>();
  private maxCacheSize = 10;
  private cacheExpiry = 5 * 60 * 1000; // 5分钟

  constructor(private resolver: ComponentFactoryResolver) {}

  // 从缓存获取或创建组件
  getOrCreate(
    componentType: Type,
    container: any,
    cacheKey?: string
  ): ComponentRef<any> {
    const key = cacheKey || componentType.name;

    // 尝试从缓存获取
    const cached = this.cache.get(key);
    if (cached && this.isValidCacheEntry(cached)) {
      cached.lastUsed = Date.now();
      cached.useCount++;

      // 重新附加到容器
      container.insert(cached.componentRef.hostView);

      console.log(`从缓存获取组件: ${key}`);
      return cached.componentRef;
    }

    // 创建新组件
    const factory = this.resolver.resolveComponentFactory(componentType);
    const componentRef = container.createComponent(factory);

    // 添加到缓存
    this.cache.set(key, {
      componentRef,
      lastUsed: Date.now(),
      useCount: 1
    });

    // 清理过期缓存
    this.cleanupCache();

    console.log(`创建新组件并缓存: ${key}`);
    return componentRef;
  }

  // 检查缓存是否有效
  private isValidCacheEntry(entry: CacheEntry): boolean {
    const age = Date.now() - entry.lastUsed;
    return age < this.cacheExpiry;
  }

  // 清理缓存
  private cleanupCache(): void {
    if (this.cache.size <= this.maxCacheSize) return;

    // 按最后使用时间排序
    const entries = Array.from(this.cache.entries())
      .sort((a, b) => a[1].lastUsed - b[1].lastUsed);

    // 移除最旧的条目
    const toRemove = entries.slice(0, entries.length - this.maxCacheSize);
    toRemove.forEach(([key]) => {
      this.cache.get(key)?.componentRef.destroy();
      this.cache.delete(key);
      console.log(`清理缓存组件: ${key}`);
    });
  }

  // 手动清理组件
  destroyComponent(key: string): void {
    const entry = this.cache.get(key);
    if (entry) {
      entry.componentRef.destroy();
      this.cache.delete(key);
    }
  }

  // 清空所有缓存
  clearCache(): void {
    this.cache.forEach(entry => entry.componentRef.destroy());
    this.cache.clear();
    console.log('组件缓存已清空');
  }

  // 获取缓存统计
  getCacheStats(): { size: number, entries: any[] } {
    const entries = Array.from(this.cache.entries()).map(([key, entry]) => ({
      key,
      age: Date.now() - entry.lastUsed,
      useCount: entry.useCount
    }));

    return {
      size: this.cache.size,
      entries
    };
  }
}

7. 安全考虑

安全警告:
  • 代码注入:动态加载不受信任的代码可能导致XSS攻击
  • 权限控制:确保只有授权用户可以加载特定组件
  • 输入验证:严格验证动态组件的输入数据
  • 沙箱隔离:考虑使用Web Workers或iframe隔离动态代码
  • 内容安全策略:配置CSP限制动态脚本加载
  • 审计日志:记录所有动态组件加载操作
// secure-component-loader.service.ts - 安全组件加载器
import { Injectable, SecurityContext } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Injectable({
  providedIn: 'root'
})
export class SecureComponentLoaderService {
  private trustedSources = new Set<string>([
    'https://trusted-cdn.com',
    'https://internal-server.com'
  ]);

  constructor(private sanitizer: DomSanitizer) {}

  // 安全地加载动态组件
  async loadSecureComponent(
    componentUrl: string,
    container: any
  ): Promise<boolean> {
    // 1. 验证URL
    if (!this.isTrustedSource(componentUrl)) {
      console.error('不信任的组件源:', componentUrl);
      return false;
    }

    // 2. 清理URL
    const safeUrl = this.sanitizer.sanitize(
      SecurityContext.URL,
      componentUrl
    );

    if (!safeUrl) {
      console.error('URL清理失败:', componentUrl);
      return false;
    }

    try {
      // 3. 使用沙箱加载(如果支持)
      const sandbox = this.createSandbox();

      // 4. 加载组件
      const module = await this.loadModuleFromUrl(safeUrl);

      // 5. 验证组件签名(如果有)
      if (!this.verifyComponentSignature(module)) {
        throw new Error('组件签名验证失败');
      }

      // 6. 安全检查
      if (!this.securityCheck(module)) {
        throw new Error('安全检查失败');
      }

      // 7. 创建组件
      const componentRef = container.createComponent(module.component);

      // 8. 监控组件行为
      this.monitorComponent(componentRef);

      return true;

    } catch (error) {
      console.error('安全加载失败:', error);
      return false;
    }
  }

  private isTrustedSource(url: string): boolean {
    try {
      const urlObj = new URL(url);
      return this.trustedSources.has(urlObj.origin);
    } catch {
      return false;
    }
  }

  private createSandbox(): any {
    // 创建沙箱环境(简化示例)
    // 实际实现可能需要iframe或Web Worker
    return {
      // 沙箱配置
    };
  }

  private async loadModuleFromUrl(url: string): Promise<any> {
    // 使用动态import,但需要webpack配置支持
    // 或者使用System.import、fetch+eval等
    const response = await fetch(url);
    const code = await response.text();

    // 严格限制执行环境
    const module = this.executeInSandbox(code);
    return module;
  }

  private executeInSandbox(code: string): any {
    // 在实际应用中,应该使用更安全的执行方式
    // 这里仅作示例
    try {
      const exports = {};
      const module = { exports };

      // 创建安全上下文
      const safeContext = {
        console,
        setTimeout,
        clearTimeout,
        // 限制其他全局对象访问
      };

      // 使用Function构造函数而不是eval
      const func = new Function('module', 'exports', 'context', `
        with(context) {
          ${code}
        }
      `);

      func(module, exports, safeContext);
      return module.exports;
    } catch (error) {
      throw new Error(`执行失败: ${error.message}`);
    }
  }

  private verifyComponentSignature(module: any): boolean {
    // 验证组件签名(如果有)
    // 实际应用中可能需要数字签名验证
    return true; // 简化实现
  }

  private securityCheck(module: any): boolean {
    // 执行安全检查
    const component = module.component || module.default;

    // 检查是否包含危险操作
    const blacklist = [
      'eval',
      'Function',
      'document.write',
      'innerHTML',
      'outerHTML',
      'insertAdjacentHTML'
    ];

    const componentCode = component.toString();
    return !blacklist.some(keyword => componentCode.includes(keyword));
  }

  private monitorComponent(componentRef: any): void {
    // 监控组件行为
    const originalMethods = {};

    // 包装危险方法
    const dangerousMethods = ['ngOnInit', 'ngAfterViewInit'];

    dangerousMethods.forEach(method => {
      if (componentRef.instance[method]) {
        originalMethods[method] = componentRef.instance[method];

        componentRef.instance[method] = (...args: any[]) => {
          console.log(`监控: 组件调用 ${method}`);

          // 添加安全检查
          try {
            return originalMethods[method].apply(componentRef.instance, args);
          } catch (error) {
            console.error(`组件方法 ${method} 执行错误:`, error);
            // 可以在此处销毁组件
          }
        };
      }
    });
  }
}

8. 测试动态组件

// dynamic-loader.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DynamicLoaderComponent } from './dynamic-loader.component';
import { AlertComponent } from './alert.component';
import { ComponentFactoryResolver } from '@angular/core';

describe('DynamicLoaderComponent', () => {
  let component: DynamicLoaderComponent;
  let fixture: ComponentFixture<DynamicLoaderComponent>;
  let resolver: ComponentFactoryResolver;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [DynamicLoaderComponent, AlertComponent],
      providers: [ComponentFactoryResolver]
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(DynamicLoaderComponent);
    component = fixture.componentInstance;
    resolver = TestBed.inject(ComponentFactoryResolver);
    fixture.detectChanges();
  });

  it('应该创建组件', () => {
    expect(component).toBeTruthy();
  });

  it('应该能够动态加载Alert组件', () => {
    spyOn(component as any, 'loadComponent').and.callThrough();

    component.loadAlert();
    fixture.detectChanges();

    expect((component as any).loadComponent).toHaveBeenCalledWith('alert', jasmine.any(Object));
  });

  it('应该正确设置组件输入属性', () => {
    const testTitle = '测试标题';
    const testMessage = '测试消息';

    component.loadAlert();
    fixture.detectChanges();

    // 检查组件是否被创建
    const alertElement = fixture.nativeElement.querySelector('app-alert');
    expect(alertElement).toBeTruthy();

    // 检查输入属性
    // 需要通过组件实例来验证
  });

  it('应该能够销毁组件', () => {
    component.loadAlert();
    fixture.detectChanges();

    expect(fixture.nativeElement.querySelector('app-alert')).toBeTruthy();

    component.destroyComponent();
    fixture.detectChanges();

    expect(fixture.nativeElement.querySelector('app-alert')).toBeFalsy();
  });

  it('应该在组件销毁时清理资源', () => {
    spyOn(component, 'ngOnDestroy').and.callThrough();

    component.loadAlert();
    fixture.detectChanges();

    fixture.destroy();

    expect(component.ngOnDestroy).toHaveBeenCalled();
  });

  it('应该处理组件加载失败', () => {
    spyOn(console, 'error');
    spyOn(resolver, 'resolveComponentFactory').and.throwError('加载失败');

    component.loadAlert();
    fixture.detectChanges();

    expect(console.error).toHaveBeenCalled();
  });
});

// 模拟测试组件
@Component({
  template: '<div>测试组件</div>'
})
class TestComponent {}

describe('DynamicComponentLoading', () => {
  it('应该允许在运行时创建组件', () => {
    const resolver = TestBed.inject(ComponentFactoryResolver);
    const fixture = TestBed.createComponent(DynamicLoaderComponent);

    const factory = resolver.resolveComponentFactory(TestComponent);
    const componentRef = fixture.componentInstance.container.createComponent(factory);

    expect(componentRef).toBeTruthy();
    expect(componentRef.instance).toBeInstanceOf(TestComponent);
  });
});
应用场景总结:
  • 插件系统:应用扩展和功能模块
  • 仪表板:可配置的小部件系统
  • 表单生成器:动态表单字段和验证器
  • 工作流引擎:动态流程节点和操作
  • CMS系统:动态内容块和编辑器
  • 多租户应用:租户特定UI组件
  • A/B测试:动态变体UI组件
  • 主题系统:动态主题组件切换