Reputation: 53
after upgrading angular to the latest version and stroybook, I have a problem with a component that worked fine before.
TS file:
const defaultConfig = {
h1: "test",
h2: "test",
message: "test",
hasBackButton: false,
submitButtonLabel: "test",
cancelButtonLabel: "test",
width: 550,
} as const;
export default {
title: "Test component",
component: TestComponent,
decorators: [
moduleMetadata({
imports: [ConfirmationModalModule, HttpClientModule],
})
],
args: defaultConfirmationModalArgs
} as Meta;
export const Properties: Story<typeof defaultConfirmationModalArgs> = (args) => ({
props: {
config: {
h1: args.h1,
h2: args.h2,
message: args.message,
hasBackButton: args.hasBackButton,
submitButtonLabel: args.submitButtonLabel,
cancelButtonLabel: args.cancelButtonLabel,
width: args.width,
}
});
html:
<modal-header
[h1]="config.h1"
[h2]="config.h2"
[hasBackButton]="config.hasBackButton"
></modal-header>
<modal-body>
</modal-body>
<modal-footer>
</modal-footer>
html file which worked fine before, now I get an error and I have to wrap the component in *ngIf="config"
can help me?
Upvotes: 1
Views: 2637
Reputation: 65
In Storybook 7 you should use the new notation to write your story. This is how it should be coded:
export const Properties: StoryObj<TestComponent> = {
name: 'Story of Test component',
args: defaultConfig
}
The rest of the code can be left as it was written.
Upvotes: 0
Reputation: 239
The same issue occured on my side after upgrading from Angular 12 to Angular 13. I think this happens always when you try to bind objects. I did further analysis inside the code of storybook and found out that the main problem is that storybook sets the bindings in the StorybookWrapperComponent in AfterViewInit. However I do not understand why this was not a problem in previous Angular versions because if you think of the whole lifecycle the same issue should have happened already in previous versions. Is anything new implemented by the updated Angular version regarding the behaviour of the lifecycle? Is it maybe stricter to nullish values?
The code of the Storybook Wrapper Component (that will render the component) is the following (as you see the OnInit is not binding the values, the AfterViewInit binds the actual values):
StorybookWrapperComponent.prototype.ngOnInit = function () {
var _this = this;
// Subscribes to the observable storyProps$ to keep these properties up to date
this.storyWrapperPropsSubscription = this.storyProps$.subscribe(function (storyProps) {
if (storyProps === void 0) { storyProps = {}; }
// All props are added as component properties
Object.assign(_this, storyProps); // --> Here the properties are assigned, but not to the actual component which is rendered
_this.changeDetectorRef.detectChanges(); // --> Here we will trigger the rendering which then creates the issue
_this.changeDetectorRef.markForCheck();
});
};
StorybookWrapperComponent.prototype.ngAfterViewInit = function () {
var _this = this;
// Bind properties to component, if the story have component
if (this.storyComponentElementRef) {
var ngComponentInputsOutputs_1 = NgComponentAnalyzer_1.getComponentInputsOutputs(storyComponent);
var initialOtherProps = getNonInputsOutputsProps(ngComponentInputsOutputs_1, initialProps);
// Initializes properties that are not Inputs | Outputs
// Allows story props to override local component properties
initialOtherProps.forEach(function (p) {
_this.storyComponentElementRef[p] = initialProps[p];
});
// `markForCheck` the component in case this uses changeDetection: OnPush
// And then forces the `detectChanges`
this.storyComponentViewContainerRef.injector.get(core_1.ChangeDetectorRef).markForCheck();
this.changeDetectorRef.detectChanges();
// Once target component has been initialized, the storyProps$ observable keeps target component inputs up to date
this.storyComponentPropsSubscription = this.storyProps$
.pipe(operators_1.skip(1), operators_1.map(function (props) {
// removes component output in props
var outputsKeyToRemove = ngComponentInputsOutputs_1.outputs.map(function (o) { return o.templateName; });
return Object.entries(props).reduce(function (prev, _a) {
var _b;
var key = _a[0], value = _a[1];
return (__assign(__assign({}, prev), (!outputsKeyToRemove.includes(key) && (_b = {},
_b[key] = value,
_b))));
}, {});
}), operators_1.map(function (props) {
// In case a component uses an input with `bindingPropertyName` (ex: @Input('name'))
// find the value of the local propName in the component Inputs
// otherwise use the input key
return Object.entries(props).reduce(function (prev, _a) {
var _b, _c;
var propKey = _a[0], value = _a[1];
var input = ngComponentInputsOutputs_1.inputs.find(function (o) { return o.templateName === propKey; });
return __assign(__assign({}, prev), (input ? (_b = {}, _b[input.propName] = value, _b) : (_c = {}, _c[propKey] = value, _c)));
}, {});
}))
.subscribe(function (props) {
// Replace inputs with new ones from props
Object.assign(_this.storyComponentElementRef, props);
// `markForCheck` the component in case this uses changeDetection: OnPush
// And then forces the `detectChanges`
_this.storyComponentViewContainerRef.injector.get(core_1.ChangeDetectorRef).markForCheck();
_this.changeDetectorRef.detectChanges();
});
}
Update This issue seems to be fixed with the last version of storybook, after upgrading from 6.4.15 to 6.4.17 everything works.
Upvotes: 1