You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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提供注入上下文,导致依赖注入失败。

修改步骤:

  1. 所有导航项(包括叶子节点和父节点)都需要用<ng-template ngbNavItem>包裹,确保NgbNavItem的注入上下文存在。
  2. 父节点使用ngbNavItem的collapsible属性,并通过ngbNavOutlet渲染子导航。
  3. 调整递归模板结构,区分叶子节点和可折叠的父节点。

修改后的模板代码:

<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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 04:35:53