Young programmer
Young programmer

Reputation: 53

Storybook problem with inputs after upgrading angular to version 13

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? enter image description here

Upvotes: 1

Views: 2637

Answers (2)

Visibly0467
Visibly0467

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

StyrianDev
StyrianDev

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

Related Questions