在 *ngIf 中加载动态组件 - createComponent 未定义

Posted

技术标签:

【中文标题】在 *ngIf 中加载动态组件 - createComponent 未定义【英文标题】:Load dynamic component within *ngIf - createComponent is undefined 【发布时间】:2020-02-27 00:35:41 【问题描述】:

当用户单击如下所示的按钮时,我正在尝试将动态组件加载到我的视图中:

<button (click)="showContent()">Show Panel</button>
<!--This starts as 'showPanel = false'-->
<div *ngIf="showPanel">
    <ng-template #ref></ng-template>
</div>

但是,当单击视图中的按钮时,我尝试运行 loadComponent(),但收到一条错误消息,指出 Cannot read property 'createComponent' of undefined

我已经查找了一些解决方案,有人建议使用QueryList&lt;ViewContainerRef&gt;,但我没有成功地让它工作。

来源:ViewContainerRef is undefined when called in ngAfterViewInit

另一个解决方案建议使用 ElementRef 并检查 chaged 状态,但即使在尝试在 ngAfterViewInit 中检查它时也始终未定义。

来源:@ViewChild in *ngIf

我正在寻找适用于 Angular 8 的选项,但我不完全确定下一步该去哪里。

代码如下:

parent.component.ts

export class ParentComponent 

@ViewChild('ref',  static: false, read: ViewContainerRef ) ref: ViewContainerRef;

showPanel = false;

loadComponent(): void 
    const factory = this.componentFactoryResolver.resolveComponentFactory(ChildComponent);
    const component = this.ref.createComponent(factory);
    component.changeDetectorRef.detectChanges();


showContent(): void 
    this.showPanel = !this.showPanel;
    this.loadContent(); // Error is thrown here, this doesn't make sense as *ngIf should now be true.

【问题讨论】:

我不明白你想如何将组件加载到自 showPanel = false; 以来尚不存在的 ref 中 您的 DOM 元素不存在,因为您在 showPanel 上以 false 开头,因此您的 ref 不存在。 @yurzui,我知道,但是当您单击按钮并运行 loadContent() 时,它会失败并出现相同的错误。我的问题不是很清楚。已更新。 @Michelangelo 和上面的 Yurzui 一样,我的问题不是很清楚我已经更新它以显示实际发生的事情 在您更改 showPanel 属性时,Angular 不会更新视图。您可以在此处使用许多选项:1)对 ViewChild 使用 setter 2)使用 ngComponentOutlet 渲染组件 3)在 this.showPanel = !this.showPanel; 之后使用 cdRef.detectChanges() 【参考方案1】:

按照 yurzui 的想法,我将如何应用它们:

Here 是 StackBlitz 示例

changeDetectorRef.detectChanges()

showContent () 
    this.showPanel = !this.showPanel;

   if (this.showPanel) 
      this.cdr.detectChanges();
      this.loadComponent();
    
  

ViewChild 的设置器

  private _ref: ViewContainerRef;

  private get ref () 
    return this._ref;
  

  @ViewChild('ref',  static: false, read: ViewContainerRef )
  private set ref (r) 
    console.log('setting ref', r)
    this._ref = r;

    if (this._ref) 
      this.loadComponent();
    
  

  showPanel = false;

  constructor (
    private cdr: ChangeDetectorRef,
    private cfr: ComponentFactoryResolver,
  )  

  loadComponent () 
    const factory = this.cfr.resolveComponentFactory(ChildComponent);
    const component = this.ref.createComponent(factory);
  

  showContent () 
    this.showPanel = !this.showPanel;
  

使用&lt;ng-container&gt;

正如您所指出的,使用ngTemplateOutlet 通常是一个很好的解决方案,但是当您处理多个组件时,在模板中执行所有这些逻辑会变得很麻烦。

我们可以利用 ViewContainerRef's API 处理来自您的组件的所有内容 (.ts file)。

<button (click)="showContent()">Show Panel</button>

<ng-container #vcr></ng-container>
@ViewChild('vcr',  static: true, read: ViewContainerRef )
 vcr: ViewContainerRef;

 showContent () 
  this.showPanel = !this.showPanel;  

  this.showPanel && this.attachComponent();

  !this.showPanel && this.removeComponent();  


private attachComponent () 
  const compFactory = this.cfr.resolveComponentFactory(ChildComponent);

  const compView = this.vcr.createComponent(compFactory);


private removeComponent () 
    this.vcr.clear();

这种方法为您提供了比您可以处理的更多的控制权! 例如,您可以使用vcr.detachvcr.insertshowPanel 变为false 后保留组件的状态。

You can find how right here.

【讨论】:

确实有很多选择!我最终使用了 changeDetection 建议。【参考方案2】:

这是因为 *ngIf 在条件评估为 false 时删除了 div 元素,这意味着子元素不存在于您的组件模板中。

您可以使用 [hidden] 代替,它只隐藏 div 元素,以便您可以通过模板引用变量访问它。

<button (click)="showContent()">Show Panel</button>
<!--This starts as 'showPanel = false'-->
<div [hidden]="!showPanel">
    <ng-template #ref></ng-template>
</div>

【讨论】:

如果不需要,我宁愿不加载组件。拥有 [hidden] 仍然意味着我必须在组件使用之前渲染它。不理想。 但是我没有说你需要在 ngAfterViewInit() 方法中加载组件,你的代码提到这一行被注释了。 我的问题写得不好,我的问题是在 ngAfterViewInit 生命周期钩子之后加载组件。它在尝试运行 loadComponent() 时抛出错误,说明 *ngIf 设置为 true 后无法读取未定义的属性“createComponent”。这个想法是不要将任何不需要的东西加载到 DOM 中。 在这种情况下,您甚至不需要用条件 div 包装 ng-template 标记,因为在用户单击“显示面板”按钮后将其替换为动态组件之前,它不会显示任何内容. 所以我从 div 中删除了 ng-template,它实际上在按钮单击时起作用,但是我需要使用 div 样式类。如果 *ngIf = true,什么会阻止组件加载?是因为它的状态变化吗?

以上是关于在 *ngIf 中加载动态组件 - createComponent 未定义的主要内容,如果未能解决你的问题,请参考以下文章

如何在复合组件中加载资源包属性文件?

NuxtJS|在头组件中加载组件

为啥在下拉列表中加载组件时不会触发 ngmodelChange 事件?

如何在反应组件中加载脚本

无法在其他类组件中加载 React 类组件

如何在 joomla 2.5 的组件中加载模块