The first it's know how make it using .css. I find this beauty Minimalist Tree View In Pure CSS
Well, with this idea we can use Angular to generate a "recursive component". but first we must take account that we need use "attribute selector" to not generate the tag of the component. Don't worry, it's only use as selector some like
selector: '[recursive]'
So, instead of write some like
<recursive></recursive>
we can use some like
<ul recursive></ul>
Well, the component is like
<li *ngFor="let item of children">
{{item.label}}
<ul recursive *ngIf="item.children"
[children]="item.children"
[parent]="self"
[level]="level!=undefined?level+1:0">
</ul>
</li>
@Component({
selector: '[recursive]', //<--the the "selector"
templateUrl:'./hello.component.html'
})
export class HelloComponent {
@Input() level: number;
@Input() label: string;
@Input() children: any;
@Input() parent: any;
self=this;
}
Well, I add the properties "level" and "parent" that I don't use in the code but can be interesting if our component make "some-more" that show the tree.
And our app.component.html is some like
<ul class="tree" recursive [children]="data"></ul>
Be carefull. I need use ViewEncapsulation.None in the app.component to transport the .css
See that we give [children] the own data
where, e.g.
data = [{label: "Parent1", children: [
{ label: "Parent 1's only child" }
]},
{label:"Parent2"},
{label:"Parent3", children: [
{ label: "1st Child of 3" ,children:[
{label:"1st grandchild"},
{label:"2nd grandchild"}
]},
{ label: "2nd Child of 3" },
{ label: "3rd Child of 3" }
]
},
{
label: "Parent 4", children: [
{ label: "Parent 4's only child" }
]}]
You can see in this stackblitz
Updated If we want add open/close capacity, it's easy adding a span and using [ngClass] for the three cases: doc, open and close, so our recursive.html becomes
<li *ngFor="let item of children">
<span [ngClass]="!item.children?
'doc':item.isOpen?'open':'close'"
(click)="item.isOpen=!item.isOpen"></span>
{{item.label}}
<ul recursive *ngIf="item.children && item.isOpen"
[children]="item.children"
[parent]="self"
[level]="level!=undefined?level+1:0">
</ul>
</li>
I used a .css like
.doc,.open,.close
{
display:inline-block;
width:1rem;
height:1rem;
background-size: 1rem 1rem;
}
.doc
{
background-image:url('...');
}
.open
{
background-image:url('...');
}
.close
{
background-image:url('...');
}
The updated stackblitz
Updated 2 I don't like so much use ViewEncapsulation.None, so we can make a work-around and our recursive component becomes like
<ul class="tree" *ngIf="level==undefined">
<ng-container *ngTemplateOutlet="tree;context:{children:children}">
</ng-container>
</ul>
<ng-container *ngIf="level!=undefined">
<ng-container *ngTemplateOutlet="tree;context:{children:children}">
</ng-container>
</ng-container>
<ng-template #tree let-children="children">
<li *ngFor="let item of children">
<span [ngClass]="!item.children?'doc':item.isOpen?'open':'close'" (click)="item.isOpen=!item.isOpen"></span>{{item.label}}
<ul recursive *ngIf="item.children && item.isOpen"
[children]="item.children"
[parent]="self"
[level]="level!=undefined?level+1:0">
</ul>
</li>
</ng-template>
That's. The first use a <ul class="tree">...</ul>
, the others not add the <ul>
Again, the final stackblitz