Angular Storybook中ngTemplateOutlet模板无法访问item上下文问题
问题
在为设计系统的Select组件编写Storybook文档时,通过[itemTemplate]="itemTestTemplate"传入自定义模板后,模板无法访问上下文里的item变量。相关代码如下:
组件TS代码
import {Component, EventEmitter, Input, Output, TemplateRef} from '@angular/core'; @Component({ selector: 'wgw-select', templateUrl: './select.component.html', styleUrls: ['./select.component.scss'] }) export class SelectComponent<T extends {id: string | number}> { @Input() itemTemplate!: TemplateRef<any>; @Input() items: T[]; @Input() selectedItem: T; @Output() selectedItemChange: EventEmitter<T> = new EventEmitter<T>(); selectValue(newValue: T) { this.selectedItem = newValue; this.selectedItemChange.emit(this.selectedItem); } equals(item1: T, item2: T) { return item1?.id === item2?.id } }
组件HTML模板代码
<div class="dropdown"> <button class="fs-4" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false"> <ng-container *ngTemplateOutlet="this.itemTemplate; context: {item: selectedItem}"></ng-container> <wgw-icon name="chevron-down" class="ms-4"></wgw-icon> </button> <div class="dropdown-menu mt-5" aria-labelledby="dropdownMenuButton1"> <div class="dropdown-item fs-4" *ngFor="let item of items" (click)="selectValue(item)" [class.selected]="equals(selectedItem, item)"> <wgw-icon [name]="'check'" *ngIf="equals(selectedItem, item)" class="me-4"></wgw-icon> <ng-container *ngTemplateOutlet="this.itemTemplate; context: {item: item}"></ng-container> </div> </div> </div>
已编写的Story代码
import {SelectComponent} from '@design-system/atom/select/select.component'; import {moduleMetadata, Story} from '@storybook/angular'; import {IconComponent} from '@design-system/atom/icon/icon.component'; export default { title: 'Atom/Select', component: SelectComponent, args: { }, decorators: [ moduleMetadata({ declarations: [IconComponent] }), ], } const Template: Story<SelectComponent<any>> = (args) => ({ props: args, template: ` <div> <ng-template #itemTestTemplate let-item="item"> {{ item?.name }} </ng-template> <wgw-select [items]="items" [itemTemplate]="itemTestTemplate"></wgw-select> </div> ` }) export const Select = Template.bind({}); Select.args = { items: [ {id: 0, name: 'item1'}, {id: 1, name: 'item2'}, {id: 2, name: 'item3'} ], selectedItem: {id: 0, name: 'item1'} }
原因分析
问题核心在于两个点:
- Angular模板变量接收语法错误:
let-item="item"的写法会让Angular尝试从模板自身的上下文中查找变量,而非使用*ngTemplateOutlet传入的上下文值。 - 组件TS中
items和selectedItem未初始化,可能导致初始渲染时出现undefined,进一步引发模板渲染异常。
修复方案
1. 修正Story中的模板变量接收语法
将Story模板里的let-item="item"改为let-item,Angular会自动匹配传入上下文里的同名键值:
const Template: Story<SelectComponent<any>> = (args) => ({ props: args, template: ` <div> <ng-template #itemTestTemplate let-item> {{ item?.name }} </ng-template> <wgw-select [items]="items" [itemTemplate]="itemTestTemplate" [(selectedItem)]="selectedItem"></wgw-select> </div> ` })
2. 初始化组件TS中的输入属性
为避免初始渲染时的undefined问题,给items和selectedItem设置初始值:
export class SelectComponent<T extends {id: string | number}> { @Input() itemTemplate!: TemplateRef<any>; @Input() items: T[] = []; // 初始化空数组 @Input() selectedItem!: T; // 若允许未选中状态,可改为`selectedItem?: T` @Output() selectedItemChange: EventEmitter<T> = new EventEmitter<T>(); // ... 其他代码保持不变 }
3. 优化组件HTML模板(可选)
模板中可以省略this.直接引用组件属性,让代码更简洁规范:
<div class="dropdown"> <button class="fs-4" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false"> <ng-container *ngTemplateOutlet="itemTemplate; context: {item: selectedItem}"></ng-container> <wgw-icon name="chevron-down" class="ms-4"></wgw-icon> </button> <div class="dropdown-menu mt-5" aria-labelledby="dropdownMenuButton1"> <div class="dropdown-item fs-4" *ngFor="let item of items" (click)="selectValue(item)" [class.selected]="equals(selectedItem, item)"> <wgw-icon [name]="'check'" *ngIf="equals(selectedItem, item)" class="me-4"></wgw-icon> <ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container> </div> </div> </div>
完成以上修改后,模板就能正确访问到传入的item变量,组件也能正常初始化和工作。
内容的提问来源于stack exchange,提问作者Benoit Bonavia
相关产品推荐
相关产品推荐

