I'm trying to use ng-bootstrap's ngbNav
directive to create a navigation tree of arbitrary depth. What I have almost works, but I'm fighting with what are think are Angular's template scoping around injection contexts. When I try to load what I have at the moment, I get the following error:
Component update failed: R3InjectorError(Standalone[_AppComponent])[_NgbNavItem -> _NgbNavItem -> _NgbNavItem]: NullInjectorError: No provider for _NgbNavItem!
I'm using Angular v19, and standalone components. I'm using ng-bootstrap v18.0.0.
Here's my template code:
<nav ngbNav #nav="ngbNav" orientation="vertical" class="nav-pills">
<ng-container *ngTemplateOutlet="recursiveTree; context: { list: urlInfoTable, depth: 0 }"></ng-container>
</nav>
<ng-template #recursiveTree let-list="list" let-depth="depth">
<ng-container *ngFor="let urlInfo of list">
<a *ngIf="urlInfo.isLeaf" ngbNavLink [routerLink]="[urlInfo.url]">{{ urlInfo.name }}</a>
<ul *ngIf="urlInfo.children$ !== null">
<ng-container
*ngTemplateOutlet="recursiveTree; context: { list: urlInfo.children$ | async, depth: depth + 1 }"
></ng-container>
</ul>
</ng-container>
</ng-template>
<main id="sideNavContent">
<router-outlet />
</main>
And the relevant bits from my component code:
export class NavigationComponent {
public urlInfoTable: UrlInfo[] = [
{ id: "1", url: "/", name: "Home", isLeaf: true, children$: null },
{ id: "2", url: "/users/self", isLeaf: true, name: "Self", children$: null },
{ id: "3", name: "Organizations", isLeaf: false, children$: this.getOrgs() }
];
}
interface UrlInfo {
id: string;
isLeaf: boolean;
name: string;
url?: string;
children$: Observable<UrlInfo[]> | null;
}
...and the getOrgs()
method returns a placeholder Observable<UrlInfo[]>
at the moment.
I have noticed that if I remove ngbNavLink
from the template, the error goes away (along with all the Nav functionality, of course). Looking at ng-bootstrap's code for the NgbNavLink directive, it even makes sense--the first thing it tries to do is resolve the NgbNavItem
dependency. What I don't understand is how--if at all?--can I ensure that it has the required context here?
First convert the ng-container
to a div
or span
with the navLink
directive:
<div *ngFor="let urlInfo of list" ngbNavItem>
<a *ngIf="urlInfo.isLeaf" ngbNavLink [routerLink]="[urlInfo.url]"
>{{ urlInfo.name }}</a
>
...
Then move the ng-template
inside the nav
so that it has access to ngbNav
injection token.
<nav ngbNav #nav="ngbNav" orientation="vertical" class="nav-pills">
<ng-container
*ngTemplateOutlet="recursiveTree; context: { list: urlInfoTable, depth: 0 }"
></ng-container>
<ng-template #recursiveTree let-list="list" let-depth="depth">
<div *ngFor="let urlInfo of list" ngbNavItem>
<a *ngIf="urlInfo.isLeaf" ngbNavLink [routerLink]="[urlInfo.url]"
>{{ urlInfo.name }}</a
>
<ul *ngIf="urlInfo.children$ !== null">
<ng-container
*ngTemplateOutlet="recursiveTree; context: { list: urlInfo.children$ | async, depth: depth + 1 }"
></ng-container>
</ul>
</div>
</ng-template>
</nav>