Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular @ContentChildren doesn´t work with <ng-template>

I am trying to create a Tree component in Angular, something like this:

@Component({
  selector: 'app-tree',
  template: `
    <div style.padding-left.px="{{depth*20}}">
      <ng-content></ng-content>
    </div>
  `
})
export class TreeComponent implements AfterContentInit {
  private _depth = 0;
  get depth() {
    return this._depth;
  }
  set depth(value: number) {
    this._depth = value;
    this.updateChildren();
  }

  @Input() isRoot: boolean;

  @ContentChildren(TreeComponent) children: QueryList<TreeComponent>;

  ngAfterContentInit() {
    if (this.isRoot) {
      console.log('i am root', this.children.filter(tree => tree !== this).length);
      this.updateChildren();
    }
  }

  private updateChildren() {
    this.children
      .filter(tree => tree !== this)
      .forEach(branch => {
        branch.depth = this.depth + 1;
      });
  }
}

This works fine when using the component with markup. Something like this:

<app-tree [isRoot]="true">
  Label 1
  <app-tree>
    Label 1.1
  </app-tree>
  <app-tree>
    Label 1.2
    <app-tree>
      Label 1.2.1
    </app-tree>
  </app-tree>
  <app-tree>
    Label 1.3
  </app-tree>
</app-tree>

But it fails when rendering the Tree based on data, for example:

<ng-template #treeTemplate let-tree let-root="isRoot">
  <app-tree *ngFor="let node of tree" [isRoot]="root">
    {{ node.text }}
    <ng-container *ngTemplateOutlet="treeTemplate; context:{$implicit:node.children}"></ng-container>
  </app-tree>
</ng-template>
<ng-container *ngTemplateOutlet="treeTemplate; context:{$implicit:treeModel, isRoot:true}"></ng-container>

This code renders the tree nodes recursively based on a model, the model could be something like this:

treeModel = [
  {
    text: 'Lorem ipsum',
    children: [
      {
        text: 'Lorem ipsum',
        children: [
          {
            text: 'Lorem ipsum',
            children: [
              { text: 'Lorem ipsum' },
              { text: 'Lorem ipsum' },
              { text: 'Lorem ipsum' }
            ]
          },
          { text: 'Lorem ipsum' },
          { text: 'Lorem ipsum' },
          { text: 'Lorem ipsum' }
        ]
      },
      { text: 'Lorem ipsum' },
      { text: 'Lorem ipsum' },
      { text: 'Lorem ipsum' }
    ]
  }
];

This doesn't work because of rendering the tree from a template. It appears that when wrapping the component in a ng-template, @ContentChildren fails to find the children rendered from a template.

Is this expected? Any possible solutions, workarounds?

Another issue (although less problematic) is that ContentChildren includes the host component; thus this.children.filter(tree => tree !== this).

Link to Plunker: https://embed.plnkr.co/jRXk8eI0mz7db6eliMoj/

like image 260
nest Avatar asked Feb 10 '26 19:02

nest


1 Answers

There is an Open issue in Angular for this problem.

In the link the person who reports the issue provides a workaround, but I find it rather cumbersome and I believe it can lead to poor performance.

like image 186
nest Avatar answered Feb 13 '26 08:02

nest