Angular获取动态创建的组件的viewContainerRef

Posted

技术标签:

【中文标题】Angular获取动态创建的组件的viewContainerRef【英文标题】:Angular get viewContainerRef of dynamicly created component 【发布时间】:2018-12-01 05:27:18 【问题描述】:

在一个组件中,我确实使用服务来加载 JSON 数据。根据这些具有树结构的数据,我想动态创建新组件:

import  Component, AfterViewInit, OnDestroy, ComponentFactoryResolver, ViewContainerRef, Input, ViewChild  from '@angular/core';

import  MenuComponent  from '../menu/menu.component';
import  RowComponent  from '../row/row.component';

import  DynamicComponentInterface  from '../../interfaces/dynamicComponentInterface';
import  SlotDirective  from './slot.directive';

import  ContentService  from '../../services/content.service';
import  DynamicComponentsHostService  from '../../services/dynamicComponentsHost.service';



@Component(
  selector: 'app-slot',
  templateUrl: './slot.component.html',
  styleUrls: ['./slot.component.css']
)
export class SlotComponent implements AfterViewInit, OnDestroy  
  @Input() id:number;
  @ViewChild(SlotDirective) slot: SlotDirective;
  private slotDataTree;
  private componentRef;
  private contentReadySubscription;

  constructor(
    private contentService : ContentService,
    private componentFactoryResolver : ComponentFactoryResolver,
    private viewContainerRef: ViewContainerRef,
    private dynamicComponentsHostService: DynamicComponentsHostService

  ) 

  

  ngAfterViewInit()
    // get JSON data with TreeStructure from Service
    if(this.contentService.stateReady)
      this.slotDataTree = this.contentService.getCurrentBySlotId(this.id);
      this.renderContentTree();
    
    else
      this.contentReadySubscription = this.contentService.getCurrentPageContentReadyObserver().subscribe(stateReady => 
    if(stateReady )
      this.slotDataTree = this.contentService.getCurrentBySlotId(this.id);
      this.renderContentTree();
    
      );
    
  

  renderContentTree()
    if(this.slotDataTree)


      let componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.dynamicComponentsHostService.getDynamicComponentType(this.slotDataTree.type));
      let viewContainerRef = this.slot.viewContainerRef;
      let componentRef = viewContainerRef.createComponent(componentFactory);


      (<DynamicComponentInterface>componentRef.instance).data = this.slotDataTree;

    // Works like a charm.
    // now I want to create children for the newly created component
    // so I need the viewContainerRef of componentRef



    // this delivers a viewRef
      console.log(componentRef.hostView);

    // this delivers sometimes a viewContainerRef in console, but a comile error that _viewRef does not exists

      console.log(componentRef._viewRef._viewContainerRef);

    // I cannot do this in the dynamicly rendered components class its self, because of circular dependencies that would occour then
      /*
      if(this.slotDataTree.children.length)
    for(var i = 0; i < this.slotDataTree.children.length; i++)

      let componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.dynamicComponentsHostService.getDynamicComponentType(this.slotDataTree.children[i].type));
      let viewContainerRef = componentRef._viewRef._viewContainerRef;
      let componentRef = viewContainerRef.createComponent(componentFactory);

    
      
      */
      if(this.contentReadySubscription)
    this.contentReadySubscription.unsubscribe();
      
    
  

  ngOnDestroy() 
    if(this.contentReadySubscription)
      this.contentReadySubscription.unsubscribe();
    
  

我可以动态创建组件。但是我也为创建的组件创建子组件。意味着我需要我刚刚创建的那个组件的 viewRefContainer。

由于循环依赖,我无法在习惯于动态创建的组件类中动态创建组件。

我该如何解决这个问题?

谢谢

【问题讨论】:

你有没有解决这个问题?我现在也有同样的需求 是的,现在我使用了一个服务,它承载了所有可能必须创建的组件类型。然后我使用创建第一个动态组件的根组件。这个根组件订阅了动态创建的组件的观察者。动态创建的组件的观察者将其 viewRefContainer 发送回根组件。根组件使用 viewRefContainer 来创建 childComponents 等等。 感谢您的回答!我得到了它的工作:) 【参考方案1】:

我现在就是这样解决的。首先,我使用一个根组件,它是动态创建的组件树的起源。也许服务会更好:

import  Component, AfterViewInit, OnDestroy, OnInit, ComponentFactoryResolver, Input, ViewChild, ChangeDetectorRef  from '@angular/core';
import  MenuComponent  from '../menu/menu.component';
import  DynamicComponent  from '../dynamic/dynamic.component';
import  SlotDirective  from '../../directives/slot.directive';
import  ContentService  from '../../services/content.service';
import  EditService  from '../../services/edit.service';
import  DynamicComponentsHostService  from '../../services/dynamicComponentsHost.service';
import  DynamicComponentsData  from '../../classes/dynamicComponentsData';

@Component(
  selector: 'app-slot',
  templateUrl: './slot.component.html',
  styleUrls: ['./slot.component.css']
)
export class SlotComponent implements OnDestroy, OnInit  
  @Input() id:number;
  @ViewChild(SlotDirective) slot: SlotDirective;
  private componentRef;
  private contentReadySubscription;
  public slotDataTree : DynamicComponentsData = new DynamicComponentsData(,false);

  constructor(
    protected contentService : ContentService,
    private editService : EditService,
    private componentFactoryResolver : ComponentFactoryResolver,
    private dynamicComponentsHostService: DynamicComponentsHostService
  ) 
  

  ngOnInit()
    var slotData;
    if(this.contentService.stateReady)
      if( slotData = this.contentService.getCurrentBySlotId(this.id) )
        this.slotDataTree = slotData;
      
      else
        this.slotDataTree.type = 'none';
      
      this.renderContentTree();
    

    this.contentReadySubscription = this.contentService.getCurrentPageContentReadyObserver().subscribe(stateReady => 
      if(stateReady )
        if( slotData = this.contentService.getCurrentBySlotId(this.id) )
          this.slotDataTree = slotData;
        
        else
          this.slotDataTree.type = slotData;
        
        this.renderContentTree();
      
    );

    DynamicComponentsData.changed.subscribe( changeObject =>
      this.renderContentTree();
    );
  

  renderChilds(viewContainerRef,childs)
    for(var i = 0; i < childs.length; i++)
      // create the dynamic component. Use a service to get the types
      let componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.dynamicComponentsHostService.getDynamicComponentType(childs[i].type).class);
      let componentRef = viewContainerRef.createComponent(componentFactory);
      // give some data
      (<DynamicComponent>componentRef.instance).data = childs[i];
      // subscribe an event emitter
      (<DynamicComponent>componentRef.instance).sendViewContainerRef.subscribe(
        (event:any)=>
          if(event.childs)
            this.renderChilds(event.viewContainerRef, event.childs);
          
        
      );
    
  

  renderContentTree()
    let viewContainerRef = this.slot.viewContainerRef;
    viewContainerRef.clear();

    if(this.contentService.getCurrentBySlotId(this.id))
      this.renderChilds(this.slot.viewContainerRef, this.contentService.getCurrentBySlotId(this.id).children);
      if(this.contentReadySubscription)
        this.contentReadySubscription.unsubscribe();
      
    
  

  ngOnDestroy() 
    if(this.contentReadySubscription)
      this.contentReadySubscription.unsubscribe();
    
  

【讨论】:

以上是关于Angular获取动态创建的组件的viewContainerRef的主要内容,如果未能解决你的问题,请参考以下文章

Angular完全销毁动态创建的组件

子动态创建的组件与父组件之间的Angular4通信

在 Angular 中动态创建的嵌套组件

Angular 7 - 向动态创建的组件添加拖放行为

Angular 2:向动态创建的组件添加指令

Angular 5:如何使用 jsPlumb 使动态创建的组件可拖动?