Angular 2- ContentChidren of parent component are undefined when dynamically creating child component

五迷三道 提交于 2019-12-13 00:04:27

问题


I've been trying to make a tab view and thought content projection might be a good approach.I learned it from this article. I thought that I could make it dynamic by just inputting a given array of components, and they would be displayed as the page for the selected tab.

Here is my attempt at doing this:

@Component({
   selector: 'my-app',
    template:`
    <h1>Experiments</h1>
    <button class="add-button" (click)="add()">Add</button>`
})
export class App  {

  components:Array<any> = [PrepSetupTab,FinalizeTab]

  constructor(private cdr: ChangeDetectorRef,
              private compFR: ComponentFactoryResolver,
              private viewContainer: ViewContainerRef ){} 

  add():void{
        var transTabRefs: Array<any>= []

        this.components.forEach(tabComponent=>{

          let compFactory = this.compFR.resolveComponentFactory(tabComponent);
          let compRef = this.viewContainer.createComponent(compFactory);
          let tabFactory = this.compFR.resolveComponentFactory(Tab);

          let transcludedTabRef = this.viewContainer.createComponent(tabFactory,this.viewContainer.length - 1, undefined, [[compRef.location.nativeElement]]);
          transTabRefs.push(transcludedTabRef.location.nativeElement);
        })

        let tabsFactory = this.compFR.resolveComponentFactory(Tabs); // notice this is the tabs not tab
        this.viewContainer.createComponent(tabsFactory,0,undefined,[transTabRefs]);

  }


}

Side note, add() as a click handler of button isn't intended functionality other to avoid this bug: "EXCEPTION: Expression has changed after it was checked". I get this bug if I put it in any life cycle hooks.Although that's a challenge to deal with later though.

So basically I am creating the each Component in the array, and then i'm sticking them in as ng-content for each created Tab Component. Then taking each Tab component and sticking it in ng-content for Tabs Component.

The problem is Tabs Component doesn't ever find the contentChildren of the dynamically created Tab children. Here is the code for the Tabs Component where the the content children are undefined.

@Component({
  selector: 'tabs',
  template:`
    <ul class="nav nav-tabs">
      <li *ngFor="let tab of tabs" (click)="selectTab(tab)" [class.active]="tab.active">
        <a>{{tab.title}}</a>
      </li>
    </ul>
    <ng-content></ng-content>
  `
})
export class Tabs implements AfterContentInit {

  @ContentChildren(Tab) tabs: QueryList<Tab>;

  // contentChildren are set
  ngAfterContentInit() {
    // get all active tabs
    let activeTabs = this.tabs.filter((tab)=>tab.active);

    // if there is no active tab set, activate the first
    if(activeTabs.length === 0) {
      this.selectTab(this.tabs.first);
    }
  }

  selectTab(tab: Tab){
    // deactivate all tabs
    this.tabs.toArray().forEach(tab => tab.active = false);

    // activate the tab the user has clicked on.
    tab.active = true;
  }

}

It seems to be pretty clear to me that the Tab Component is created at a different time then when I need to access them as content children from the Tabs Component. I've also tried using the ChangeDetectionRef.changeDetect() but that doesn't help.

Maybe doing this all through content projection isn't easiest way or best so I am open to suggestions. Here is the plunk, thanks!


回答1:


@ContentChildren won't work for components created dynamically.

Why?

That's because candidates for projection must be known at compile time.

Here is the function that is used to calculate ContentChildren

function calcQueryValues(view, startIndex, endIndex, queryDef, values) {
    for (var /** @type {?} */ i = startIndex; i <= endIndex; i++) {
        var /** @type {?} */ nodeDef = view.def.nodes[i];
        var /** @type {?} */ valueType = nodeDef.matchedQueries[queryDef.id];
        if (valueType != null) {
            values.push(getQueryValue(view, nodeDef, valueType));
        }

It uses nodes that are declared in viewDefinition for component. When you created tabs component via createComponent and passed projectable nodes manually you didn't change viewDefinition factory for tabs component and hence angular won't be aware about dynamic nodes.

A possible solution could be manually initializing your tabs

You can observe it in this Plunkr




回答2:


Take a look on the example I give here dynamically add elements to DOM, maybe you find something helpful there for your problem. At this point in your plnkr the tabs array is empty.



来源:https://stackoverflow.com/questions/44832735/angular-2-contentchidren-of-parent-component-are-undefined-when-dynamically-cre

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!