storybook
storybook copied to clipboard
[Bug]: Error when trying to create a story for a not standalone component that extends a standalone component
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
Can you check out the latest Storybook 8 beta? AFAIK it has some Angular improvements. npx storybook@next upgrade
@valentinpalkovic thanks for the suggestion! But unfortunately it didn't help, just tried it :(
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',
};
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.
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...
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. :)