We’re all familiar with the recursive view. One of the most common components that needed this technique is the nested navigation bar. The HTML structure and UI look like this.
<ul>
<li><a href="#">Section 1</a></li>
<li>
<a href="#">Section 2</a>
<ul>
<li><a href="#">Section 2.1</a></li>
<li><a href="#">Section 2.2</a></li>
<li><a href="#">Section 2.3</a></li>
</ul>
</li>
<li>
<a href="#">Section 3</a>
<ul>
<li><a href="#">Section 3.1</a></li>
<li>
<a href="#">Section 3.2</a>
<ul>
<li><a href="#">Section 3.2.1</a></li>
<li><a href="#">Section 3.2.2</a></li>
<li>
<a href="#">Section 3.2.3</a>
<ul>
<li><a href="#">Section 3.2.3.1</a></li>
<li><a href="#">Section 3.2.3.2</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#">Section 3.3</a></li>
</ul>
</li>
</ul>
But how to make one with Angular?
Imagine I have a class NavigationModel
that represents the data above.
class NavigationModel {
public title: string;
public url?: string;
public children: NavigationModel[];
}
So the trick is straightforward, you set up a ng-template
which takes a list of NavigationModel
as the parameter. Then render the template itself with children
data if there are any children. Because children
are also a list of NavigationModel
. If you are not familiar with ngTemplateOutletContext
, see this question for more detail
app.component.html
<ul>
<ng-container
*ngTemplateOutlet="recursiveListTmpl; context:{ list: list }"
></ng-container>
</ul>
<ng-template #recursiveListTmpl let-list="list">
<li *ngFor="let item of list">
{{ item.title }}
<ul *ngIf="item.children.length > 0">
<ng-container
*ngTemplateOutlet="recursiveListTmpl; context:{ list: item.children }"
></ng-container>
</ul>
</li>
</ng-template>
app.component.ts
import { Component } from "@angular/core";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"]
})
export class AppComponent {
public list: NavigationModel[] = [
{
title: "Section 1",
children: []
},
{
title: "Section 2",
children: [
{
title: "Section 2.1",
children: []
},
{
title: "Section 2.2",
children: []
},
{
title: "Section 2.3",
children: []
}
]
},
{
title: "Section 3",
children: [
{ title: "Section 3.1", children: [] },
{
title: "Section 3.2",
children: [
{
title: "Section 3.2.1",
children: []
},
{
title: "Section 3.2.2",
children: []
},
{
title: "Section 3.2.3",
children: [
{
title: "Section 3.2.3.1",
children: []
},
{
title: "Section 3.2.3.2",
children: []
}
]
}
]
},
{
title: "Section 3.3",
children: [
{
title: "Section 3.3.1",
children: []
},
{
title: "Section 3.3.2",
children: []
}
]
}
]
}
];
}
Then you have it. Another solution is to create a new component/ directive that takes input
of NavigationModel[]
. And do exactly the same thing as the ng-template
example above.