如何用ng-bootstrap的NgbNav创建递归导航树?解决注入错误
问题
尝试用ng-bootstrap的ngbNav指令创建任意深度的导航树,当前实现接近可行,但遇到Angular模板注入上下文作用域问题,加载时出现错误:
Component update failed: R3InjectorError(Standalone[_AppComponent])[_NgbNavItem -> _NgbNavItem -> _NgbNavItem]:
NullInjectorError: No provider for _NgbNavItem!
使用环境:Angular v19独立组件、ng-bootstrap v18.0.0。
当前模板代码:
<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>
组件代码:
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; }
getOrgs()方法当前返回占位符Observable<UrlInfo[]>。移除ngbNavLink后错误消失,但失去导航功能。查看ng-bootstrap的NgbNavLink指令代码,它需要解析NgbNavItem依赖,但不清楚如何确保其拥有所需的注入上下文。
解决方案
问题根源在于ngbNavLink必须包裹在ngbNavItem组件内部,递归模板中直接使用a[ngbNavLink]时,没有对应的NgbNavItem提供注入上下文,导致依赖注入失败。
修改步骤:
- 所有导航项(包括叶子节点和父节点)都需要用
<ng-template ngbNavItem>包裹,确保NgbNavItem的注入上下文存在。 - 父节点使用
ngbNavItem的collapsible属性,并通过ngbNavOutlet渲染子导航。 - 调整递归模板结构,区分叶子节点和可折叠的父节点。
修改后的模板代码:
<nav ngbNav #nav="ngbNav" orientation="vertical" class="nav-pills"> <ng-container *ngTemplateOutlet="recursiveTree; context: { list: urlInfoTable }"></ng-container> </nav> <ng-template #recursiveTree let-list="list"> <ng-container *ngFor="let urlInfo of list"> <!-- 叶子节点:直接用ngbNavItem包裹带ngbNavLink的链接 --> <ng-template *ngIf="urlInfo.isLeaf" ngbNavItem> <a ngbNavLink [routerLink]="[urlInfo.url]">{{ urlInfo.name }}</a> </ng-template> <!-- 父节点:可折叠的navItem,内部嵌套子导航 --> <ng-template *ngIf="!urlInfo.isLeaf" ngbNavItem collapsible> <a ngbNavLink>{{ urlInfo.name }}</a> <ng-template ngbNavOutlet> <ng-container *ngIf="urlInfo.children$ | async as children"> <ng-container *ngTemplateOutlet="recursiveTree; context: { list: children }"></ng-container> </ng-container> </ng-template> </ng-template> </ng-container> </ng-template> <main id="sideNavContent"> <router-outlet /> </main>
关键说明:
- 每个导航项都必须放在
<ng-template ngbNavItem>中,这样Angular才能为ngbNavLink提供NgbNavItem的注入实例。 - 父节点使用
collapsible属性开启折叠功能,通过ngbNavOutlet作为子导航的容器,确保子导航的NgbNavItem能正确关联到父级的ngbNav上下文。 - 异步加载的子节点需要先用
async管道解析,再传入递归模板,避免上下文丢失。
内容的提问来源于stack exchange,提问作者PingZing
相关产品推荐
相关产品推荐

