storybook icon indicating copy to clipboard operation
storybook copied to clipboard

[Bug]: Error when trying to create a story for a not standalone component that extends a standalone component

Open daryakalenik opened this issue 1 year ago • 2 comments

Describe the bug

I have two components - the Parent component is standalone, the Child one - not standalone. Child extends Parent (class ChildComponent extends ParentComponent) and when I'm trying to create a story for the child I get an error Unexpected directive 'ChildComponent' imported by the module 'StorybookComponentModule'. Please add an @NgModule annotation. If I remove extending from the Child or if I add standalone: true to it the error disappears

To Reproduce

https://stackblitz.com/edit/github-qnhd3h

There's a story for Child component - you can navigate to it using storybook interface

System

System:
    OS: Windows 10 10.0.19045
    CPU: (16) x64 AMD Ryzen 7 PRO 4750G with Radeon Graphics
  Binaries:
    Node: 20.10.0 - C:\Program Files\nodejs\node.EXE
    npm: 10.2.3 - C:\Program Files\nodejs\npm.CMD <----- active
  Browsers:
    Edge: Spartan (44.19041.3636.0), Chromium (121.0.2277.112)
  npmPackages:
    @storybook/addon-essentials: ^7.6.16 => 7.6.16
    @storybook/addon-interactions: ^7.6.16 => 7.6.16
    @storybook/addon-links: ^7.6.16 => 7.6.16
    @storybook/angular: ^7.6.16 => 7.6.16
    @storybook/blocks: ^7.6.16 => 7.6.16
    @storybook/test: ^7.6.16 => 7.6.16
    storybook: ^7.6.16 => 7.6.16

Additional context

No response

daryakalenik avatar Feb 16 '24 09:02 daryakalenik

Can you check out the latest Storybook 8 beta? AFAIK it has some Angular improvements. npx storybook@next upgrade

valentinpalkovic avatar Feb 16 '24 17:02 valentinpalkovic

@valentinpalkovic thanks for the suggestion! But unfortunately it didn't help, just tried it :(

daryakalenik avatar Feb 16 '24 18:02 daryakalenik

I managed to work around this issue by creating a dummy standalone component inside the story file. And then using that component in the story.

@Component({
  standalone: true,
  imports: [RealComponentWhichExtendsOther],
  template: `
    <app-real></app-real>
  `,
})
class DummyComponent {}

export default {
  component: DummyComponent,
  title: 'RealComponent',
};

gobeli avatar Mar 28 '24 09:03 gobeli

I think I see what is happening and it does appear to be a bug.

I can't remember exactly how Angular handles the class decorator, when extending a class. If I am remembering correctly, though, it doesn't look at anything, other than the decorator that is added to the class directly. I would need to confirm, before I can say that is actually correct.

Assuming my understanding is correct, then Storybook's usage of reflectionCapabilities.annotations is incorrect. Storybook is iterating all decorators, to determine if the class is a Component, Directive or Pipe and if it is standalone. In this situation, there are two component decorators and at least one contains standalone: true, so Storybook is considering it to be a standalone component.

Marklb avatar Mar 28 '24 17:03 Marklb

Ah, I think your right. At least in my case the parent component is standalone and from what I can remember, it is the case that only the decorator on the actual component counts. I've got it to work inside the tests of the codebase by using the private method _ownAnnotations from Angular ReflectionCapabilities (https://github.com/angular/angular/blob/ddbf6bb038d101daf5280abbd2a0efaa0b7fd3a0/packages/core/src/reflection/reflection_capabilities.ts#L180C11-L180C26) and adding a Testcase. But it's probably not a good idea to use a private api...

gobeli avatar Mar 28 '24 18:03 gobeli

We have found a solution. You are welcome to try it and see if it works for you.

It's not a sustainable solution, but it would be interesting to know if it works everywhere. My testing capabilities were very limited...

Manually apply the changes from the pull request https://github.com/storybookjs/storybook/pull/27353/files#diff-4ff611f840cb60ea92c0c47c5e7fced91a9f8d4a57fea7c9b7d3e7b5b97c46ca in ./node_modules/@storybook/angular/dist/client/angular-beta/utils/PropertyExtractor.js

Replace

const isStandalone = (isComponent || isDirective) && decorators.some((d) => d.standalone)

With

 const isStandalone = !!(
      (isComponent || isDirective) &&
      [...decorators]
        .reverse()
        .find(
          (d) =>
            _a.isDecoratorInstanceOf(d, 'Component') || _a.isDecoratorInstanceOf(d, 'Directive')
        )?.standalone);

Hopefully, this will be fixed in one of the upcoming releases. :)

dario-baumberger avatar May 24 '24 09:05 dario-baumberger