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/
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With