Angular: Overwriting Input/Output properties of base component leads to incomplete metadata
See original GitHub issueDescribe the bug
StorybookWrapperComponent
does not correctly get @Input
and @Output
properties from an inherited parent component. This can lead to them getting overwritten and behaving unexpectedly.
As far as I was able to recreate this issue it’s actually a bug in how Angular handles it’s metadata. Since I don’t have high hopes that this gets fixed anytime soon in Angular is there interest in fixing this via a workaround in storybook?
I have mitigated this issue in my local branch by iterating and merging the parent prop metadata:
export const getComponentPropsDecoratorMetadata = (component: any) => {
const decoratorKey = '__prop__metadata__';
let propsDecorators: Record<string, (Input | Output)[]> =
Reflect &&
Reflect.getOwnPropertyDescriptor &&
Reflect.getOwnPropertyDescriptor(component, decoratorKey)
? Reflect.getOwnPropertyDescriptor(component, decoratorKey).value
: component[decoratorKey];
// START WORKAROUND
const parent = Reflect && Reflect.getPrototypeOf && Reflect.getPrototypeOf(component);
if (parent) {
const parentPropsDecorators = getComponentPropsDecoratorMetadata(parent);
propsDecorators = { ...parentPropsDecorators, ...propsDecorators };
}
// END WORKAROUND
return propsDecorators;
};
Please let me know if we should go down this route then I will create a PR with my fix.
To Reproduce
Add these two files anywhere in the angular example in the repo:
test.component.ts
// eslint-disable-next-line max-classes-per-file
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'hadv-storybook-test',
template: '',
})
export class BaseTestComponent {
private _value?: string;
@Input()
public get value(): string | undefined {
return this._value;
}
public set value(value: string | undefined) {
this.writeValue(value);
}
@Output()
public get valueChange(): EventEmitter<string | undefined> {
return this.#valueChange;
}
#valueChange = new EventEmitter<string | undefined>();
public writeValue(value: string | undefined) {
this._value = value;
this.valueChange.emit(value);
}
}
@Component({
selector: 'storybook-test',
template: '{{ value }} <button (click)="changeValue()">Set Values</button>',
})
export class TestComponent extends BaseTestComponent {
@Input()
public get value(): string | undefined {
return `${super.value} test`;
}
public set value(value: string | undefined) {
super.value = value;
}
public changeValue(): void {
this.value = (Math.random() * 100).toFixed(0);
}
}
test.component.stories.ts
import { Story } from '@storybook/angular';
import { TestComponent } from './test.component';
export default {
title: 'Base/Test',
component: TestComponent,
argTypes: { valueChange: { action: 'valueChange' } },
};
export const Base: Story = (args: any) => ({
props: args,
template: `
<storybook-test value="${args.value}"></storybook-test>
`,
});
Base.args = {
value: '2',
};
System
Please paste the results of npx sb@next info
here.
Additional context Add any other context about the problem here.
Issue Analytics
- State:
- Created 2 years ago
- Comments:15 (9 by maintainers)
@Olusha ok I’ve now tried to recreate your issue with this story:
child.component.stories.ts
But the behavior is exactly the same with 6.3 and 6.4-alpha.22. So my PR didn’t fix your specific issue but at least it didn’t cause it.
It’s pretty easy to get the storybook source code running and trying to debug the issue yourself. I’m not a member of the team but PRs are always welcome. Else I would suggest creating a separate bug report, as I think those issues are at least a bit different.
@shilman , yes, my first issue with Input property inheritance is fixed in the 6.4-alpha.23, but the second issue I described is still there. The problem occurs if I try to send a function that returns the object as an arg to the story. If primitive value is return by the function, everything works as expected. Link to the sandbox (https://stackblitz.com/edit/angular-ivy-lo5uhk?file=src/app/child/child.component.ts)