Angular 13中ng-bootstrap Accordion与自定义组件配合使用失效问题求助
我完全懂你这个头疼的问题——ng-bootstrap的Accordion组件确实对直接子元素的结构有严格要求,它依赖ContentChildren查询直接子节点中的NgbPanel实例来完成渲染,一旦你用自定义组件包裹了ngb-panel,或者用属性指令的方式没处理好模板关联,就会出现各种渲染异常。
先分析你之前两种尝试的问题根源
自定义组件作为独立元素的方式
当你把<custom-panel>放在<ngb-accordion>内部时,Angular会先渲染<custom-panel>这个组件,而组件内部的<ngb-panel>属于组件的私有视图,不在ngb-accordion的内容查询范围内。ngb-accordion找不到任何直接子级的NgbPanel,自然只会显示空的折叠面板容器。属性型选择器的方式
虽然你把自定义组件改成了属性指令的形式,但原来组件内部的ngbPanelHeader和ngbPanelContent模板并没有被正确关联到当前的ngb-panel实例上。这些模板是在组件的视图上下文里,而ngb-panel需要的是直接属于它自己的模板引用,所以最终面板的头部和内容都为空。
推荐的解决方案:用<ng-template>封装复用面板结构
这是最优雅且符合ng-bootstrap设计逻辑的方式,既实现了代码复用,又能让ngb-accordion直接识别到ngb-panel:
步骤1:封装面板模板
在父组件(或者单独的模板组件里)定义可复用的面板模板,用let-el接收传入的数据:
<ng-template #customPanelTpl let-el> <ngb-panel> <ng-template ngbPanelHeader> <div class="row align-items-center justify-content-center justify-content-md-between"> {{el}} </div> </ng-template> <ng-template ngbPanelContent> <!-- 这里放你的面板内容,同样可以使用el变量 --> 这是{{el}}对应的面板内容 </ng-template> </ngb-panel> </ng-template>
步骤2:在Accordion中复用模板
用<ng-container>(不会渲染成真实DOM元素)配合ngTemplateOutlet循环渲染模板,这样ngb-panel会直接成为ngb-accordion的子元素:
<ngb-accordion [closeOthers]="true"> <ng-container *ngFor="let item of someList"> <ng-template [ngTemplateOutlet]="customPanelTpl" [ngTemplateOutletContext]="{$implicit: item}" ></ng-template> </ng-container> </ngb-accordion>
备选方案:手动将自定义组件内的NgbPanel注册到Accordion
如果你一定要用自定义组件的形式,可以通过手动注册的方式让ngb-accordion识别到组件内的NgbPanel:
自定义组件代码
import { Component, Input, ViewChild } from '@angular/core'; import { NgbPanel } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'custom-panel', template: ` <ngb-panel> <ng-template ngbPanelHeader> <div class="row align-items-center justify-content-center justify-content-md-between"> {{el}} </div> </ng-template> <ng-template ngbPanelContent> 自定义组件的面板内容:{{el}} </ng-template> </ngb-panel> ` }) export class CustomPanelComponent { @Input() el: any; @ViewChild(NgbPanel) panel!: NgbPanel; }
父组件代码
import { Component, ViewChildren, AfterViewInit, ViewChild } from '@angular/core'; import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap'; import { CustomPanelComponent } from './custom-panel.component'; @Component({ template: ` <ngb-accordion #accordion [closeOthers]="true"> <custom-panel *ngFor="let item of someList" [el]="item"></custom-panel> </ngb-accordion> ` }) export class ParentComponent implements AfterViewInit { @ViewChildren(CustomPanelComponent) customPanels!: QueryList<CustomPanelComponent>; @ViewChild('accordion') accordion!: NgbAccordion; someList = ['面板1', '面板2', '面板3']; ngAfterViewInit() { // 将每个自定义组件内的NgbPanel手动添加到Accordion的面板集合中 this.customPanels.forEach(panelComp => { this.accordion.panels.push(panelComp.panel); }); // 通知Accordion更新视图 this.accordion.ngAfterContentInit(); } }
不过这种方法依赖ng-bootstrap的内部API(panels集合),未来版本可能会有变动,所以优先推荐第一种<ng-template>的方案。
内容的提问来源于stack exchange,提问作者MSalv

