描述bug
示例组件
组件
import { Component, EventEmitter, HostBinding, Input, Output } from '@angular/core';
export interface MillSelectorMill {
code: string;
description: string;
selected: boolean;
}
@Component({
selector: 'copo-mill-selector',
templateUrl: './mill-selector.component.html',
styleUrls: ['./mill-selector.component.css']
})
export class MillSelectorComponent {
@HostBinding('class.passthrough') readonly passthrough = true;
@Input() mills?: MillSelectorMill[] = [];
@Input() validateChange?: (mill: MillSelectorMill) => Promise<boolean>;
@Output() readonly millsChange = new EventEmitter<MillSelectorMill[]>(true);
private isValidating = false;
async onToggle($event: MouseEvent, millToToggle: MillSelectorMill) {
$event.preventDefault();
$event.stopPropagation();
if (!this.mills || this.isValidating) {
return false;
}
const isValidated = await this.validate(millToToggle);
if (isValidated) {
this.mills = this.mills.map(mill => {
if (mill.code === millToToggle.code) {
return {
...mill,
selected: !millToToggle.selected
};
}
return mill;
});
this.millsChange.emit(this.mills);
}
return false;
}
private async validate(millToToggle: MillSelectorMill) {
if (!this.validateChange) {
return true;
}
this.isValidating = true;
const isValidated = await this.validateChange(millToToggle);
this.isValidating = false;
return isValidated;
}
}
HTML
<div class="grid-6 copo-container">
<div class="grid-column-one-full card-info-block">
<div class="card-content-title" translate>ecall-off.mill-section</div>
<div *ngIf="mills && mills.length > 0" class="card-content-label required" translate>ecall-off.select-a-mill</div>
<div *ngIf="!mills || mills.length === 0" class="card-content-label" translate>ecall-off.no-mills</div>
<div *ngIf="mills && mills.length > 0">
<mat-checkbox *ngFor="let mill of mills; let i = index;" [checked]="mill.selected" (click)="onToggle($event, mill)"
class="mills-spacing">
{{ mill.description }}
</mat-checkbox>
</div>
</div>
</div>
故事
import { action } from '@storybook/addon-actions';
import { Meta, moduleMetadata, Story } from '@storybook/angular';
import { DEFAULT_MODULE_IMPORTS } from '../../../stories/stories.helper';
import { MillSelectorComponent, MillSelectorMill } from './mill-selector.component';
export default <Meta>{
title: 'Order/ECallOff/MillSelector',
component: MillSelectorComponent,
argTypes: {
millsChange: {
action: 'mills change'
},
validateChange: {
type: 'function',
control: 'function' // not deduced automatically, required for functions to be pass as input
}
},
decorators: [
moduleMetadata({
declarations: [ MillSelectorComponent ],
imports: DEFAULT_MODULE_IMPORTS,
providers: []
})
]
};
const TEMPLATE: Story<MillSelectorComponent> = args => ({
props: { ...args }
});
export const Default = TEMPLATE.bind({});
Default.args = {
mills: <MillSelectorMill[]>[
{
code: 'millA',
description: 'Liege',
selected: false
}, {
code: 'millB',
description: 'Florange',
selected: false
}, {
code: 'millC',
description: 'Desvres',
selected: false
}
]
};
export const Empty = TEMPLATE.bind({});
export const WithLotsOfMills = TEMPLATE.bind({});
WithLotsOfMills.args = {
mills: <MillSelectorMill[]>[
{
code: 'millA',
description: 'mill A',
selected: true
}, {
code: 'millB',
description: 'mill A',
selected: false
}, {
code: 'millC',
description: 'mill A',
selected: true
}, {
code: 'millD',
description: 'mill D',
selected: false
}, {
code: 'millE',
description: 'mill E',
selected: true
}, {
code: 'millF',
description: 'mill F',
selected: false
}, {
code: 'millG',
description: 'mill G',
selected: true
}, {
code: 'millH',
description: 'mill H',
selected: false
}, {
code: 'millI',
description: 'mill I',
selected: true
}, {
code: 'millJ',
description: 'mill J',
selected: false
}, {
code: 'millK',
description: 'mill K',
selected: true
}
]
};
async function validateMillChange(mill: MillSelectorMill): Promise<boolean> {
return new Promise<boolean>((resolve, _reject) => {
setTimeout(() => {
const isValidated = mill.code !== 'millB';
action('validate mill change')({ mill, isValidated });
resolve(isValidated);
}, 1000);
});
}
export const WithValidator = TEMPLATE.bind({});
WithValidator.args = {
validateChange: validateMillChange,
mills: <MillSelectorMill[]>[
{
code: 'millA',
description: 'Liege',
selected: false
}, {
code: 'millB',
description: 'Florange',
selected: false
}, {
code: 'millC',
description: 'Desvres',
selected: false
}
]
};
在Meta argsType属性中,如果我不强制将validateChange的control属性设置为某个值(例如函数),那么该函数就不会在WithValidator故事中设置,因此值会保持未定义。
这个问题只针对类型为函数的输入,所以我将控制值设置为'function',但实际上只需要控制属性。
我通过在浏览器中调试storybook源代码找到了解决方案。我发现了一个名为cleanArgsDecorator的函数,它检查参数是否具有action或control属性。
const cleanArgsDecorator = (storyFn, context) => {
if (!context.argTypes || !context.args) {
return storyFn();
}
const argsToClean = context.args;
context.args = Object.entries(argsToClean).reduce((obj, [key, arg]) => {
const argType = context.argTypes[key];
// Only keeps args with a control or an action in argTypes
if (argType.action || argType.control) {
return Object.assign(Object.assign({}, obj), { [key]: arg });
}
return obj;
}, {});
return storyFn();
};
如果想要像这样处理函数类型的Angular Input,请更新文档以描述这种情况,否则对于类型为函数的Angular Input,有些东西缺失。
重现问题
- 无响应*
系统
- 无响应*
附加上下文
- 无响应*
5条答案
按热度按时间de90aj5v1#
在从Angular 9升级到15以及最新的Storybook(6.5.16)后,故事中的函数出现了未定义的问题。之前可以正常工作的故事现在开始显示这些错误:
ctx.onClickHandler is not a function
将函数添加到argTypes中解决了这个问题:
在经过几天的搜索后,我刚刚发现了这个问题及其解决方法,感谢!
zd287kbt2#
我花了很多时间试图弄清楚如何将参数作为函数传递,这个问题救了我。
在此期间,我建议将其添加到文档中,直到问题得到解决。
hwamh0ep3#
这个修复对我不起作用。我向argTypes添加了
,但我仍然得到
ctx.myFunc is not a function
。我在Storybook 7.5.2上。2w2cym1i4#
你可以尝试升级到Storybook 7,并找出问题是否仍然存在?
ttisahbt5#
我正在使用Storybook 7.5.2。