Angular Nebular中nbPopover在*ngFor内传nbPopoverContext至ng-template出现undefined问题求解
*ngFor循环内的所有触发元素,全部绑定了循环外部定义的同一个#templateRef模板实例。Nebular的nbPopover组件在处理多个触发元素共用同一模板的场景时,不会为每个触发元素单独同步当前的上下文对象;叠加Angular变更检测阶段,模板里写的内联表达式{passdata:value}每次都会生成全新的对象引用,会触发Nebular内部的上下文缓存失效,最终导致ng-template接收到的passdata始终为初始的undefined值。
1. 替换模板内的内联上下文表达式
直接在模板里写[nbPopoverContext]="{passdata:value}"属于内联对象表达式,每次变更检测都会生成新的对象引用,很容易触发Nebular内部的上下文逻辑异常。
改成在组件类中预先和循环列表匹配好固定的上下文对象,再在模板中直接引用:
// 组件类内定义 popoverContextList: Array<{passdata: any}> = []; // 拿到valuelist数据后做映射 loadData() { this.valuelist = [/* 原始列表数据 */]; this.popoverContextList = this.valuelist.map(item => ({passdata: item})); }
模板中直接引用预生成的上下文数组:
<div *ngFor="let value of valuelist; let i = index"> <div [nbPopover]="templateRef" [nbPopoverContext]="popoverContextList[i]" nbTooltipStatus="basic" nbPopoverTrigger="click" > <label>Box {{i+5}}</label> </div> </div>
2. 调整ng-template的取值逻辑
Angular的ng-template在接收上下文时,如果硬写let-work="passdata"去匹配固定键名,一旦Nebular内部传递上下文时做了嵌套处理,就会直接取到undefined。
可以把模板改成直接接收整个上下文对象,再从中提取需要的passdata字段:
<ng-template #templateRef let-ctx> <div> <div class="row"> <div class="col-md-5"> <label>Name</label> </div> <div class="col-md-7"> <label>: {{ ctx?.passdata || ctx }}</label><br /> </div> </div> </div> </ng-template>
这种写法能兼容Nebular不同版本下的上下文结构变化,不会因为键名匹配错位取到空值。
3. 给*ngFor增加trackBy优化
Angular的*ngFor默认会用对象引用做DOM复用判断,列表变动时可能会出现DOM元素和上下文绑定错位的问题。
给循环加上基于索引的trackBy函数,固定每个DOM元素和对应上下文的绑定关系:
<div *ngFor="let value of valuelist; let i = index; trackBy: trackByIndex">
组件类中实现对应的trackBy方法:
trackByIndex(index: number) { return index; }
4. 兼容性最优的写法:循环内部独立定义模板
如果上面的调整还是有取值异常,说明Nebular的nbPopover在多元素共用同一模板实例时本身存在上下文同步的设计缺陷,这时候直接把popover模板移到循环内部,让每个触发元素拥有独立的模板实例,从根源上避免跨元素的模板复用冲突:
<div *ngFor="let value of valuelist; let i = index"> <!-- 每个循环项独立定义自己的popover模板,不共用外部实例 --> <ng-template #itemPopoverTpl let-work="passdata"> <div> <div class="row"> <div class="col-md-5"> <label>Name</label> </div> <div class="col-md-7"> <label>: {{ work }}</label><br /> </div> </div> </div> </ng-template> <div [nbPopover]="itemPopoverTpl" [nbPopoverContext]="{passdata: value}" nbTooltipStatus="basic" nbPopoverTrigger="click" > <label>Box {{i+5}}</label> </div> </div>
这种写法不需要额外调整上下文生成逻辑,也不存在跨元素的上下文同步问题,是实际开发中遇到同类问题时最稳妥的处理方式。
内容的提问来源于stack exchange,提问作者Loran

