storybook: Storybook and angular library: Cannot read property 'selector' of undefined

Describe the bug When I generate a component in an angular library (ng11), and I am going to use it in a storybook, it shows me the error of:

Cannot read property 'selector' of undefined.

To Reproduce https://github.com/krbaio3/sb-lib-issue

System System: OS: macOS 11.3.1 CPU: (16) x64 Intel® Core™ i9-9980HK CPU @ 2.40GHz Binaries: Node: 15.14.0 - ~/.nvm/versions/node/v15.14.0/bin/node Yarn: 1.22.10 - /usr/local/bin/yarn npm: 7.11.2 - ~/.nvm/versions/node/v15.14.0/bin/npm Browsers: Chrome: 90.0.4430.93 Firefox: 88.0 Safari: 14.1 npmPackages: @storybook/addon-actions: ^6.2.9 => 6.2.9 @storybook/addon-essentials: ^6.2.9 => 6.2.9 @storybook/addon-links: ^6.2.9 => 6.2.9 @storybook/angular: ^6.2.9 => 6.2.9

Additional context

Create an angular project: ng new design-system --create-application=false --prefix=ds --style=scss

Create a angular-lib: ng generate library pattern-lib --prefix=pl

Change the name of design-system/projects/pattern-lib/package.json to @css/pattern-lib

Change the path property of design-system/tsconfig.json from pattern-lib to @css/pattern-lib

Generate the component ng generate component button --project=pattern-lib

My button component is:

import { Component, Input, OnInit } from '@angular/core';

@Component({
  selector: 'pl-button',
  template: `<button [attr.is-pink]="pink" [ngClass]="{'make-pink': pink}">{{label ? "😎 " + label : "No Label provided
  🧐"}}</button>`,
  style: [`
button {
  background: blue;
  padding: 1rem 2rem;
  border-radius: 3px;
  appearance: none;
  border: 0;
  -webkit-appearance: none;
  -moz-appearance: none;
  font-size: 1.5rem;
  letter-spacing: 1px;
  color: white;
  box-shadow: 0 4px 10px rgba(55, 55, 55, 0.3),
    0 6px 35px rgba(55, 55, 200, 0.7);
  cursor: pointer;

  &.make-pink {
    background: #ff00a2;
    box-shadow: 0 4px 10px rgba(55, 55, 55, 0.3),
      0 6px 35px rgb(200 55 150 / 70%);
  }
}`
]
})
export class ButtonComponent {

  @Input('label') label: string | null;
  @Input('pink') pink: boolean;

}

Change tsconfig properties of design-system/projects/pattern-lib/tsconfig.lib.json. Add this property:

{
"angularCompilerOptions": {
    "enableIvy": false,
    "skipTemplateCodegen": true,
    "strictMetadataEmit": true,
    "enableResourceInlining": true
  },
}

Generate the lib dist: ng build

Init the storybook with npx -p @storybook/cli sb init --type angular

Generate my custom story: MyButton.stories.ts

import {Meta, Story} from '@storybook/angular/types-6-0'

// import { ButtonComponent } from '@css/pattern-lib';

import { ButtonComponent } from 'projects/pattern-lib/src/public-api';

export default {
  title: 'Custom/Buttons',
  component: ButtonComponent,
  argTypes: {
    label: {
      control: 'text'
    }
  }
} as Meta;

const Template: Story<ButtonComponent> = (args: ButtonComponent) => ({
  component: ButtonComponent,
  props: args
});

export const FancyBlueButton = Template.bind({});

FancyBlueButton.args = {
  label: 'Button',
};

export const FancyPinkButton = Template.bind({});

FancyBlueButton.args = {
  label: 'Pink version',
  pink: true,
}

I run: npm run storybook

If I run with this code, (import { ButtonComponent } from 'projects/pattern-lib/src/public-api’😉 the storybook works.

But, if I change the import (commented) import { ButtonComponent } from ‘@css/pattern-lib’; the storybook fails, with the error Cannot read property ‘selector’ of undefined

image

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 9
  • Comments: 72 (17 by maintainers)

Commits related to this issue

Most upvoted comments

Hey @shilman - This issue is still being experienced on Angular 14, 15, and 16 with Storybook 7+ when using npm 7+. I have created a reproduction based on @John-Zimmer’s instructions here: Reproduction Repository.

If you’d prefer I create a new issue with this reproduction, I can also do that instead.

If additional information is needed, please let me know.

I’m able to reliably reproduce this. This issue seems to occur when working in an Angular project with multiple projects in the workspace and dependencies being installed in the individual projects (likely due to npm 7 installing peer dependencies by default now). I’m not sure why the peer dependencies being installed causes this, but hopefully this helps narrow in on the root cause.

Steps to reproduce:

  1. Generate a new Angular workspace and generate a new library project (copied from Angular’s docs) ng new my-workspace --no-create-application cd my-workspace ng generate library my-lib
  2. Initialize storybook npx storybook@next init
  3. Run storybook and confirm that everything is working as expected npm run storybook
  4. Run npm install in the project folder cd projects/my-lib npm install
  5. Run storybook again and the error occurs cd ../../ npm run storybook
  6. Remove node_modules and install the project’s dependencies using npm 4-6 peer dependency handling rm projects/my-lib/node_modules cd projects/my-lib npm install --legacy-peer-deps
  7. Run storybook again and things are working as expected again cd ../../ npm run storybook

TL;DR: short term fix is to remove node_modules from project folders and install dependencies with the --legacy-peer-deps if necessary

In my own case, I was getting that error because I accidentally installed the dependencies inside the library and just having the node_modules folder throws me that error.

I generally never comment on GitHub but thought to help get this issue raised.

Using: NodeJs: 16.20.0 Npm: 8.19.4 Angular: ^16.0.4 (takes 16.1.1) storybook: ~7.0.22

I got the error listed in the main post during running the storybook website trying to render a component.

After deleting all package lock and node modules folder, and doing npm install —legacy-peer-reps it fixed all my issues after running storybook.

I can confirm the problem using storybook inside an angular-workspace with more projects, the workaround of @John-Zimmer in https://github.com/storybookjs/storybook/issues/14828#issuecomment-1523875596 works for me.

In my case I don’t need to add flags to npm, but just removing the node_modules from the project directory and doing again a fresh npm install in the workspace root, fixes the problem. Maybe this is related to my version of npm being a little older.

My environment is:

Storybook 7.3.2 
Angular CLI: 15.2.9
Node: 16.14.2
Package Manager: npm 8.5.0
OS: linux x64

I did some debugging and in my case the problem is in the function computesTemplateFromComponent: the ngComponentMetadata is undefined.

 const ngComponentMetadata = getComponentDecoratorMetadata(component);  //undefined for me

Debugging a little more inside the getComponentDecoratorMetadata function:

export const getComponentDecoratorMetadata = (component: any): Component | undefined => {
  const decorators = reflectionCapabilities.annotations(component); //correctly populated

  return decorators.reverse().find((d) => d instanceof Component); //failing  on the test function
};

I see the decorators const correctly populated with a decorator object, but then the subsequent find() returns undefined.

I hope this could help identify the problem.

Hi, I’m facing the same issue with Angular 15.2.0 and storybook 7.0.6

I’m getting the same error here: Angular 13.3 Storybook 6.5.5

I am at Angular 13 with Storoybook 6.4.19 and getting the same error. My stories are outside .stories folder

image

Hello. The same problem. It seems that angular in aot mode is removing the annotation from the component (maybe that’s not the point). Important note - the component is included as a package in node_modules

However, in the component itself, this information is present. I made a simple replacement

var ngComponentMetadata = component.decorators[0].args[0]; //NgComponentAnalyzer_1.getComponentDecoratorMetadata(component);

image And its works.

storybook 6.3.4, angular 12.1.1

In addition

Here is class, imported from node_modules/primeng looks like: image There is no annotations property, but decorators exist

And here class from “project src” looks like image Here annotations property is present

@atropo

I did some debugging and in my case the problem is in the function computesTemplateFromComponent: the ngComponentMetadata is undefined.

 const ngComponentMetadata = getComponentDecoratorMetadata(component);  //undefined for me

Debugging a little more inside the getComponentDecoratorMetadata function:

export const getComponentDecoratorMetadata = (component: any): Component | undefined => {
  const decorators = reflectionCapabilities.annotations(component); //correctly populated

  return decorators.reverse().find((d) => d instanceof Component); //failing  on the test function
};

I see the decorators const correctly populated with a decorator object, but then the subsequent find() returns undefined. I hope this could help identify the problem.

From what I can tell, this seems to be the likely source of the bug. As per https://stackoverflow.com/a/63937850/5243309 and https://stackoverflow.com/q/41587865/5243309, it seems that instanceof Component will fail when using two node_modules/@angular folders, or when a library has its own instance of node_modules/@angular because it has Angular listed in dependencies instead of peerDependencies. ( link#3 )

Which explains why npm install --legacy-peer-deps appears to solve the issue for some people.

The solution seems to be to replace all the code using instanceOf with something like this: decorator.constructor.name === factory.prototype.constructor.name.

Initially, I thought the same, and I already prepared a PR to not use instanceof checks but instead rely on some type of information, which is appended during compilation to the decorator properties.

I talked to the Angular core team, and they warned me to support this case since having multiple instances of Angular in the application is a quite broken state, and many things are likely to malfunction. The only valid solution, therefore, is to guarantee that the application loads one instance of Angular.

So I thought about the reproduction, which was provided by @John-Zimmer. When creating an Angular workspace with some applications and libraries, each library might have its dependencies, which we must install. Since npm’s default behavior is to install peer dependencies, a npm install in a library folder will install @angular/core as well. This is NOT what we want. The whole workspace should refer to one @angular/core instance, not multiple ones. So what happens if we use npm’s workspace feature here? Let’s go to the root package.json and let’s add all project folders as a workspace:

{
 ...
 "workspaces": [
    "projects/*"
  ],
 ...
}

Then, a npm install is only executed at the root level, never in a library folder. Dependencies will automatically be hoisted to the root’s node_module folder, and conflicting dependencies will remain in the lib’s node_modules. This guarantees that we always have one instance of @angular/core installed and used. Does anyone know, whether there are some arguments against this approach?

NX, on the other side, has its Single Version Policy, where all dependencies are installed in the root folder. Hence, the mentioned issues with having multiple instances of Angular should never occur. But there seem to be issues, and I am curious whether @yayassa-tootelo or @DanielAlcaraz could provide a reproduction with nx. But also, in this case, I suggest using the workspace feature of the used package manager to resolve this issue.

You guys will have this and probably other issues if you try to render your library components by pointing to a compiled library. Storybook doesn’t work well with that, and like I said, this introduces one unnecessary step to your development workflow. I’m able to reproduce the issue when I import the file directly from the library. image

I don’t recommend this approach. Please, import your component directly from its source code. I recommend you create an alias in your root folder tsconfig.json for your entry points and point them to the entry point public-api.

{
  "compilerOptions": {
    "paths": {
      "my-lib": ["projects/my-lib/src/public-api"]
    }
  }
}

If you import your components from "my-lib" like that you shouln’t have this problem. See: Angular Library + Storybook

@computergnome99 did you create a new issue for this? Seems still valid as we are also still seeing this with Angular 15.2.0, Storybook 7.0.6, NPM 8.19.3 and Windows.

image

Facing this issue with Storybook@7.0.7 and Angular@15.x in an Angular Library project. Is there a reliable workaround for this issue with Storybook 7 yet?

So doing tests with a basic component, when loading it into SB directly it works but when loading from the library it does not seem to get the meta data.

Component in SB folder image

Component in Libs 2021-09-17_10-41

Olé!! I just released https://github.com/storybookjs/storybook/releases/tag/v6.4.0-alpha.32 containing PR #15874 that references this issue. Upgrade today to the @next NPM tag to try it out!

npx sb upgrade --prerelease

Closing this issue. Please re-open if you think there’s still more to do.

Maybe it helps anyone: I had the same issue. I have a Angular mono repo for libraries and storybook installed at the root. I accidentally run npm i once within a library folder, so that I had a node_modules folder at the root and within the library. Deleting node_modules within the library and only run npm i at root fixed it for me.

Since the 7.5.0 storybook release, it is now possible to use the argsToTemplate method for property and event Bindings.

In our case, this has been used as a workaround for the error Cannot read property 'selector' of undefined.

Here is an example of how to use it :

import { argsToTemplate } from '@storybook/angular';

export const ExampleRange: Story = {
  name: 'Default',
  args: {
    stringProp: "Foo",
    objectProp : {foo: "bar"},
    arrayProp: ["Foo", "Bar"]
  },
  render: (args) => ({
    props: args,
    template: `<app-my-input ${argsToTemplate(args)}></app-my-input>`
  })
};

Using this on all of our stories seems to work, and we haven’t found any problems at the moment.

Hope this helps.


Versions : Storybook Version: 7.5.0 Node Version: 18.16.0 npm Version: 9.5.1 Angular 16.2.8

@atropo

I did some debugging and in my case the problem is in the function computesTemplateFromComponent: the ngComponentMetadata is undefined.

 const ngComponentMetadata = getComponentDecoratorMetadata(component);  //undefined for me

Debugging a little more inside the getComponentDecoratorMetadata function:

export const getComponentDecoratorMetadata = (component: any): Component | undefined => {
  const decorators = reflectionCapabilities.annotations(component); //correctly populated

  return decorators.reverse().find((d) => d instanceof Component); //failing  on the test function
};

I see the decorators const correctly populated with a decorator object, but then the subsequent find() returns undefined.

I hope this could help identify the problem.

From what I can tell, this seems to be the likely source of the bug. As per https://stackoverflow.com/a/63937850/5243309 and https://stackoverflow.com/q/41587865/5243309, it seems that instanceof Component will fail when using two node_modules/@angular folders, or when a library has its own instance of node_modules/@angular because it has Angular listed in dependencies instead of peerDependencies. ( link#3 )

Which explains why npm install --legacy-peer-deps appears to solve the issue for some people.

The solution seems to be to replace all the code using instanceOf with something like this: decorator.constructor.name === factory.prototype.constructor.name.

Hi guys, I’m facing the same error (TypeError: Cannot read properties of null (reading ‘selectors’)) while I’m not using Storybook, just Angular with some packages, so I think the issue can be related to not just Storybook. My current project uses Angular 13.1.2, and the error occurs with one component during local development.

I’m getting the same error as @anuragpathak21. Angular v13.2, Storybook v6.4.19. My components are in an Angular library within an empty workspace. The component stories are outside the .stories folder.

Just ran into the issue, the bug is still present in the latest stable release of SB

Well, the issue was solved for me on Angular 11 LTS. However after suffering a lot with this issue and other issues I realized trying to consume from a compiled library creates LOTS of issues on storybook, so I’d highly recommend against that. This will only make your workflow unnecessarily more complex. So I edited out some private info from the develpment workflow my team will follow and made it generic for you guys.

Generic Storybook Angular Workflow

Notice how we are not consuming from a library when it comes to developing the components on storybook. We are consuming directly from the library source code and webpack does a good job at compliling it for the storybook purposes. Unfortunately I can’t share the code, but find me on the Storybook Discord server if you need any help.

I would submit a patch from @KondakovArtem observations, but I didn’t get past the yarn test in the Contribution Guide. Maybe because I’m on windows… idk

Anyway, here is an ugly solution if anyone needs a workaround other than accessing the library components directly (not built), which I personally don’t like. I want the devs to access components using the built library entry points.

package.json

{
  "scripts": {
    "build-storybook": "npm run docs:json && build-storybook",
    "postinstall": "node postinstall" // <-- add this line
  }
}

Create the postinstall script, or add this piece of code if you already have one. postinstall.js

var fs = require('fs')

// Workaround to fix function getComponentDecoratorMetadata from storybook 6.3.7.
// See https://github.com/storybookjs/storybook/issues/14828.
// TODO: Remove this as soon as issue is solved in a future release.
var file = './node_modules/@storybook/angular/dist/ts3.9/client/preview/angular-beta/utils/NgComponentAnalyzer.js'
fs.readFile(file, 'utf8', function (err,data) {
  if (err) {
    return console.log(err);
  }
  var result = data.replace(
    '    return (decorators || []).find(function (d) { return d instanceof core_1.Component; });',
    'if (!decorators) return component.decorators[0].args[0]; return (decorators || []).find(function (d) { return d instanceof core_1.Component; });'
  );

  fs.writeFile(file, result, 'utf8', function (err) {
    if (err) return console.log(err);
  });
});

I do recommend you freeze your storybook at 6.3.7 or whatever version your using which doesn’t have the fix yet. This might break or break your code in different versions. That replace is working nicely for me.

Facing the same issue in mdx.