storybook [Bug]: Angular函数输入在组件中未从故事参数设置 翻译结果:[Bug]:Angular组件中的函数输入未从故事参数设置

hi3rlvi2  于 5个月前  发布在  Angular
关注(0)|答案(5)|浏览(129)

描述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,有些东西缺失。

重现问题

  • 无响应*

系统

  • 无响应*

附加上下文

  • 无响应*
de90aj5v

de90aj5v1#

在从Angular 9升级到15以及最新的Storybook(6.5.16)后,故事中的函数出现了未定义的问题。之前可以正常工作的故事现在开始显示这些错误:
ctx.onClickHandler is not a function
将函数添加到argTypes中解决了这个问题:

argTypes: {
  onClickHandler: { type: 'function', control: 'function'},

在经过几天的搜索后,我刚刚发现了这个问题及其解决方法,感谢!

zd287kbt

zd287kbt2#

我花了很多时间试图弄清楚如何将参数作为函数传递,这个问题救了我。
在此期间,我建议将其添加到文档中,直到问题得到解决。

hwamh0ep

hwamh0ep3#

这个修复对我不起作用。我向argTypes添加了

myFunc: {
    type: 'function',
    control: 'function'
  }

,但我仍然得到 ctx.myFunc is not a function 。我在Storybook 7.5.2上。

2w2cym1i

2w2cym1i4#

你可以尝试升级到Storybook 7,并找出问题是否仍然存在?

ttisahbt

ttisahbt5#

我正在使用Storybook 7.5.2。

相关问题