Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
krbaio3 opened this issue May 6, 2021 · 77 comments
Open

Comments

@krbaio3
Copy link

krbaio3 commented May 6, 2021

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(R) Core(TM) 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

@ebakep
Copy link

ebakep commented Jun 2, 2021

any updates ? faced the same issue

@NatalyIvanova
Copy link

Facing the same issue in mdx.

@ThibaudAV ThibaudAV self-assigned this Jun 3, 2021
@krbaio3
Copy link
Author

krbaio3 commented Jun 8, 2021

Hi @ThibaudAV!

do you need more info?

@ThibaudAV
Copy link
Contributor

I started to look at it but several things seem to be misconfigured or suspect in the example repos

Especially here :

https://github.com/krbaio3/sb-lib-issue/blob/master/tsconfig.json#L15
https://github.com/krbaio3/sb-lib-issue/blob/master/stories/Mybutton.stories.ts#L5

the path should match and it should not point to the dist folder no ?
with

      "@css/pattern-lib": ["projects/pattern-lib/src/public-api.ts"]

tsconfig.json
and

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

Mybutton.stories.ts

and with this version of storybook ^6.3.0-beta.10

it works on my side :)

This configuration also seems suspicious to me
https://github.com/krbaio3/sb-lib-issue/blob/master/.storybook/tsconfig.json#L2
I wonder if

{
  "extends": "../tsconfig.json",
  "exclude": [
    "../src/test.ts",
    "../src/**/*.spec.ts",
    "../projects/**/*.spec.ts"
  ],
  "include": ["../src/**/*", "../projects/**/*"],
  "files": ["./typings.d.ts"]
}

is not enough. but this does not change the way storybook works

@manbearwiz
Copy link
Contributor

I ran into this same issue. Storybook is not able to find the component annotations in this function.

Reflect.getOwnPropertyDescriptor(component, decoratorKey)

image

@kroeder
Copy link
Member

kroeder commented Jul 8, 2021

@manbearwiz what Storybook and Angular version are you using?

@manbearwiz
Copy link
Contributor

manbearwiz commented Jul 13, 2021

I can recreate that error with sb6.3.4 + ng11.2.14. I was able to resolve by using sb6.1.21 + ng11.2.14. Haven't yet bisected to determine which specific version between those introduced the bug.

Edit: Confirmed upgrading from sb6.1 to sb6.2.0 recreates the error.

@manbearwiz
Copy link
Contributor

I see a flag was added from sb6.2, angularLegacyRendering. Tried enabling that but get the same Cannot read property 'selector' of undefined error.

@KondakovArtem
Copy link

KondakovArtem commented Jul 18, 2021

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

@varadharajan007
Copy link

Any idea on which upcoming version will have fix for this issue ?

@saulodias
Copy link

saulodias commented Aug 14, 2021

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.

saulodias added a commit to saulodias/storybook that referenced this issue Aug 18, 2021
saulodias pushed a commit to saulodias/storybook that referenced this issue Aug 18, 2021
saulodias pushed a commit to saulodias/storybook that referenced this issue Aug 18, 2021
@KondakovArtem
Copy link

KondakovArtem commented Aug 19, 2021

Good afternoon, Here is my solution to this problem. The script runs before launching the storybook. The script replaces the contents of the NgComponentAnalyzer.js file in and creates a backup nearby. You can compare what has been changed Yes it works on 6.3.7.

Again. this is an ugly solution but it works

   "storybook:fix": "ts-node -P tsconfig.gen.json storybook-fix.ts",
import fs from 'fs-extra';
import { resolve } from 'path';
import { createHash } from 'crypto';

const FIX_CONTENT = `"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __spreadArrays = (this && this.__spreadArrays) || function () {
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
            r[k] = a[j];
    return r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getComponentDecoratorMetadata = exports.getComponentPropsDecoratorMetadata = exports.isComponent = exports.isDeclarable = exports.getComponentInputsOutputs = void 0;
var core_1 = require("@angular/core");
/**
 * Returns component Inputs / Outputs by browsing these properties and decorator
 */
exports.getComponentInputsOutputs = function (component) {
    var _a, _b;
    var componentMetadata = exports.getComponentDecoratorMetadata(component);
    var componentPropsMetadata = exports.getComponentPropsDecoratorMetadata(component);
    var initialValue = {
        inputs: [],
        outputs: [],
    };
    // Adds the I/O present in @Component metadata
    if (componentMetadata && componentMetadata.inputs) {
        (_a = initialValue.inputs).push.apply(_a, componentMetadata.inputs.map(function (i) { return ({ propName: i, templateName: i }); }));
    }
    if (componentMetadata && componentMetadata.outputs) {
        (_b = initialValue.outputs).push.apply(_b, componentMetadata.outputs.map(function (i) { return ({ propName: i, templateName: i }); }));
    }
    if (!componentPropsMetadata) {
        return initialValue;
    }
    // Browses component properties to extract I/O
    // Filters properties that have the same name as the one present in the @Component property
    return Object.entries(componentPropsMetadata).reduce(function (previousValue, _a) {
        var _b, _c;
        var propertyName = _a[0], value = _a[1][0];
        var valProto = value.prototype || value.__proto__ || {};
        if (value instanceof core_1.Input || valProto.ngMetadataName === 'Input') {
            var inputToAdd = {
                propName: propertyName,
                templateName: (_b = value.bindingPropertyName) !== null && _b !== void 0 ? _b : propertyName,
            };
            var previousInputsFiltered = previousValue.inputs.filter(function (i) { return i.templateName !== propertyName; });
            return __assign(__assign({}, previousValue), { inputs: __spreadArrays(previousInputsFiltered, [inputToAdd]) });
        }
        if (value instanceof core_1.Output || valProto.ngMetadataName === 'Output') {
            var outputToAdd = {
                propName: propertyName,
                templateName: (_c = value.bindingPropertyName) !== null && _c !== void 0 ? _c : propertyName,
            };
            var previousOutputsFiltered = previousValue.outputs.filter(function (i) { return i.templateName !== propertyName; });
            return __assign(__assign({}, previousValue), { outputs: __spreadArrays(previousOutputsFiltered, [outputToAdd]) });
        }
        return previousValue;
    }, initialValue);
};
exports.isDeclarable = function (component) {
    if (!component) {
        return false;
    }
    var decoratorKey = '__annotations__';
    var decorators = Reflect.getOwnPropertyDescriptor(component, decoratorKey)
        ? Reflect.getOwnPropertyDescriptor(component, decoratorKey).value
        : component[decoratorKey];
    return !!(decorators || []).find(function (d) { return d instanceof core_1.Directive || d instanceof core_1.Pipe || d instanceof core_1.Component; });
};
exports.isComponent = function (component) {
    if (!component) {
        return false;
    }
    var decoratorKey = '__annotations__';
    var decorators = Reflect.getOwnPropertyDescriptor(component, decoratorKey)
        ? Reflect.getOwnPropertyDescriptor(component, decoratorKey).value
        : component[decoratorKey];
    return (decorators || []).some(function (d) { return d instanceof core_1.Component; });
};
/**
 * Returns all component decorator properties
 * is used to get all \`@Input\` and \`@Output\` Decorator
 */
exports.getComponentPropsDecoratorMetadata = function (component) {
    var decoratorKey = '__prop__metadata__';
    var propsDecorators = Reflect &&
        Reflect.getOwnPropertyDescriptor &&
        Reflect.getOwnPropertyDescriptor(component, decoratorKey)
        ? Reflect.getOwnPropertyDescriptor(component, decoratorKey).value
        : component[decoratorKey];

    if (!propsDecorators) {
        var propsDecorators = {};
        for (var key in component.propDecorators) {
            if (component.propDecorators.hasOwnProperty(key)){
                propsDecorators[key] = [component.propDecorators[key][0].type]
            }
        }
    }
    return propsDecorators;
};
/**
 * Returns component decorator \`@Component\`
 */
exports.getComponentDecoratorMetadata = function (component) {
    var decoratorKey = '__annotations__';
    var decorators = Reflect &&
        Reflect.getOwnPropertyDescriptor &&
        Reflect.getOwnPropertyDescriptor(component, decoratorKey)
        ? Reflect.getOwnPropertyDescriptor(component, decoratorKey).value
        : component[decoratorKey];
    if (!decorators) {
        return component.decorators[0].args[0];
    }
    return (decorators || []).find(function (d) { return d instanceof core_1.Component; });
};`;

const FIX_FILE = 'NgComponentAnalyzer.js';
const PATH = `node_modules/@storybook/angular/dist/ts3.9/client/preview/angular-beta/utils`;
const CHECK_HASH = ['0be5cd4bed1d63d21a865a14707131ff', 'e0c243b9118ecf34576a8ed5d2f5e238'];

(async () => {
    console.log('Start fixing storybook...');
    let path = resolve(PATH, FIX_FILE);

    if (!fs.existsSync(path)) {
        path = resolve('../../', PATH, FIX_FILE);
        if (!fs.existsSync(path)) {
            throw new Error('Missing fixing file');
        }
    }

    const code = await fs.readFile(path, 'utf-8');

    const hash = createHash('md5').update(code).digest('hex');
    const fixHash = createHash('md5').update(FIX_CONTENT).digest('hex');
    console.log(hash);
    if (hash === fixHash) {
        console.log('Storybook is already fixed');
        return;
    }
    if (!CHECK_HASH.includes(hash)) {
        throw new Error(
            `File ${path} was modified by developers. New hash is ${hash}, prev hash ${CHECK_HASH}`,
        );
    }
    console.log('Fixing is done');
    await fs.writeFile(`${path}_backup`, code);
    await fs.writeFile(path, FIX_CONTENT);
})();

saulodias added a commit to saulodias/storybook that referenced this issue Aug 20, 2021
@shilman
Copy link
Member

shilman commented Aug 24, 2021

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.

@shilman shilman closed this as completed Aug 24, 2021
@A-Industry
Copy link

Unfortunately I still run into this issue with Storybook 6.4.0-alpha.32, Angular 12 and webpack 5:

Cannot read property 'selector' of undefined TypeError: Cannot read property 'selector' of undefined at Object.push../node_modules/@storybook/angular/dist/ts3.9/client/preview/angular-beta/ComputesTemplateFromComponent.js.exports.computesTemplateFromComponent (http://localhost:6006/vendors-node_modules_storybook_addon-actions_dist_esm_preset_addArgs_js-generated-other-entry-b72747.iframe.bundle.js:101518:30) at prepareMain (http://localhost:6006/vendors-node_modules_storybook_addon-actions_dist_esm_preset_addArgs_js-generated-other-entry-b72747.iframe.bundle.js:102991:52) at http://localhost:6006/vendors-node_modules_storybook_addon-actions_dist_esm_preset_addArgs_js-generated-other-entry-b72747.iframe.bundle.js:102981:39 at http://localhost:6006/vendors-node_modules_storybook_addon-actions_dist_esm_preset_addArgs_js-generated-other-entry-b72747.iframe.bundle.js:102978:20 at cleanArgsDecorator (http://localhost:6006/vendors-node_modules_storybook_addon-actions_dist_esm_preset_addArgs_js-generated-other-entry-b72747.iframe.bundle.js:103013:12) at http://localhost:6006/vendors-node_modules_storybook_addon-actions_dist_esm_preset_addArgs_js-generated-other-entry-b72747.iframe.bundle.js:102975:30 at http://localhost:6006/vendors-node_modules_storybook_addon-actions_dist_esm_preset_addArgs_js-generated-other-entry-b72747.iframe.bundle.js:102978:20 at http://localhost:6006/vendors-node_modules_storybook_addon-actions_dist_esm_preset_addArgs_js-generated-other-entry-b72747.iframe.bundle.js:103050:17 at http://localhost:6006/vendors-node_modules_storybook_addon-actions_dist_esm_preset_addArgs_js-generated-other-entry-b72747.iframe.bundle.js:100467:21 at http://localhost:6006/vendors-node_modules_storybook_addon-actions_dist_esm_preset_addArgs_js-generated-other-entry-b72747.iframe.bundle.js:102975:30

These are the packages that are installed.

"@storybook/addon-actions": "^6.4.0-alpha.32", "@storybook/addon-essentials": "^6.4.0-alpha.32", "@storybook/addon-links": "^6.4.0-alpha.32", "@storybook/addon-storysource": "^6.4.0-alpha.32", "@storybook/angular": "^6.4.0-alpha.32", "@storybook/builder-webpack5": "^6.4.0-alpha.32", "@storybook/manager-webpack5": "^6.4.0-alpha.32",

I noticed that internally Storybook or angular seems to be using Typescript 3.9 (See first line of stack trace: ...storybook/angular/dist/ts3.9/) while at project level Typescript 4.3.5 seems to be installed. Could this mismatch be a cause of the issue?

@saulodias
Copy link

saulodias commented Aug 30, 2021

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.

@A-Industry
Copy link

Thnx for your reply. We are also running this directly against the source, but no luck with Angular 12.2.2. Previously when we were on Angular 9 with Webpack4 storybook worked correctly.

@wesself
Copy link

wesself commented Sep 17, 2021

Also still experiencing issues with our angular library project, Any chance this can be opened again?

@shilman shilman reopened this Sep 17, 2021
@shilman
Copy link
Member

shilman commented Sep 17, 2021

@saulodias @ThibaudAV any ideas why the fix didn't work?

@wesself
Copy link

wesself commented Sep 17, 2021

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

@shilman
Copy link
Member

shilman commented Jun 9, 2023

thanks @computergnome99 @tlaukkanen !!!

@jesse-schein
Copy link

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.

@computergnome99
Copy link

@jesse-schein Thanks for the confirmation!

@DanielAlcaraz
Copy link

@jesse-schein solution works with npm. But in our monorepo we are using pnpm and I cannot find a way to make it work. We are using the last versions of Angular and Storybook with Nx.

@yayassa-tootelo
Copy link

Hello, I'm facing the same issue with Nx, I tried to use storybook in the libs folder.

Angular: 16.1.0
Storybook: 7.1.0
Nodejs: 16.16.0

@atropo
Copy link

atropo commented Aug 21, 2023

I can confirm the problem using storybook inside an angular-workspace with more projects, the workaround of @John-Zimmer in #14828 (comment) 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.

@EliezerB123
Copy link

EliezerB123 commented Sep 21, 2023

I can confirm this error, but I have a bit more information.

We started having this error after moving from Angular 12 to Angular 15, but, well, we ALSO decided to migrate to Storybook v7 at the same time.

So I'm in the unique position to confirm that this happens in both 6.5.16, AND 7.3.2, in the same project, after migrating to Angular 15.

Angular CLI: 15.2.8
Node: 16.20.2
Package Manager: npm 8.19.4
OS: win32 x64

Storybook: Both 6.5.16 AND 7.3.2, (But 7.3.2 only has this issue in a builded PROD, not dev. While 6.5.16 has this issue in DEV too.)

Running npm install --legacy-peer-deps didn't work for me. Any chance someone could post their package.json?

@manuelkroiss
Copy link

Did anyone successfully solve this issue?

I am facing the same problem as @EliezerB123.

Storybook Version: 7.4.5
Node Version: 18.17.0
npm Version: 8.3.0

Starting storybook in dev mode works perfectly fine. Running a production build and deploying the result files leads to the error.

However, I encountered the following:

When I use a story with the component attribute, the error occurs.
Example:

export Default {
   component: ButtonComponent
} as Meta;

Stories where I used a template instead of the component reference work as expected.
Example:

const Template: StoryFn<ButtonComponent> = (args: ButtonComponent) => ({
  props: {
    ...args,
  },
  template: '<ic-one-button></ic-one-button>'
});

@EliezerB123
Copy link

EliezerB123 commented Oct 4, 2023

@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.

@Alex85651
Copy link

Alex85651 commented Oct 11, 2023

Hi, i'm facing the same problem. It occurs only when running a production build :'(
This error appearred when i migrated from 7.0 to 7.4.6.
Here are the versions I use now :

Storybook Version: 7.4.6
Node Version: 18.16.0
npm Version: 9.5.1
Angular 16.2

@grankovsky
Copy link

grankovsky commented Oct 13, 2023

HI, found solution.
Change imports for stand alone at .stories.ts
from: import { Component } from '@xm-ngx/components/component';
to : import { Component } from './component.component';

I hope this could help.

@valentinpalkovic
Copy link
Contributor

valentinpalkovic commented Oct 19, 2023

@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.

@RenaudC5
Copy link

RenaudC5 commented Oct 25, 2023

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

@leogouveia
Copy link

@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, an 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.

Is there a repo or a stackblitz/codesandbox with working code?

@AmbrosiaDevelopments
Copy link

The only thing that fixed it for me was switching enableProdMode to false in angular.json. Not amazing but quick work around until this gets figured out for sure. I'm not using a monorepo either.

@shaman-apprentice
Copy link

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.

@antoine-carruana
Copy link

Is a solution in the works ?
The only solution working for us actually is to set a template instead of using the component directly, but it's not a solution for long term.

Versions :
Storybook Version: 7.6.7
Node Version: 18.18.2
npm Version: 9.8.1
Angular 17.0.8

@ghost
Copy link

ghost commented Feb 7, 2024

Screenshot 2024-02-08 104949
This is still happening. Tried a few options
Node v18.18.0
Storybook: 7.6.4

  1. Created a Angular library v16.2.0. Added a simple button.
  2. Created a sample app that uses the library from no.1. Added storybook here.
  3. Imported the button via dependencies.
  4. Encountering this issue when deployed

  1. Created a Angular library v16.2.0. Added a simple button.
  2. Added storybook here
  3. Imported the button via file dependencies.
  4. Still encountering this issue when deployed

Another clue: It works in my local for both approach. Error shows up when I deploy this.
Update: I've fix it now.

  • removed node_modules inside the library folders
  • removed the "peerDependencies" property in each of the package.json of the libraries

@valentinpalkovic
Copy link
Contributor

@ranceroxas Have you read my comment here: #14828 (comment). Please make sure that only one instance of Angular is used in the monorepo. Otherwise, disabling prod mode like mentioned in other comments might help as well.

@valentinpalkovic
Copy link
Contributor

@vasa-ae, Can you tell me how your repository structure looks? If you use a mono repo, does this comment help? The main issue is that you likely have multiple Angular instances in your repository in different node_module folders. Ensure that just one instance of @angular dependencies is installed, and then the issue should go away.

@valentinpalkovic
Copy link
Contributor

Thank you for providing all this information.

First, I want to clarify that loading stories from "node_modules" isn't officially supported. We often explicitly exclude running builder plugins/loaders for node_module files because of performance reasons.

Second, could you run npm why @angular/core or yarn why @angular/core, depending of your package manager.

Could you also initialize Storybook in your library @aea/angular-shared-library instead? Does the error disappear if you would do so (also for testing purposes)?

@Jintus
Copy link

Jintus commented May 24, 2024

Hi everyone, I'm facing the same issue using Angular@16, pnpm workspace and StoryBook@8.

I have made a reproductible repo here.

To try it out:

  1. pnpm install on the root workspace
  2. pnpm start

Hope it will help for debugging.

@valentinpalkovic I have make sure that, in the Angular UI lib, angular is a peerDependency and with a pnpm why @angular/core there is only 1 version of angular installed 16.2.12.

I have no error in dev it's only with the builded storybook. (You can try pnpm storybook)

@Jintus
Copy link

Jintus commented May 31, 2024

Hi everyone, I'm facing the same issue using Angular@16, pnpm workspace and StoryBook@8.

I have made a reproductible repo here.

To try it out:

  1. pnpm install on the root workspace
  2. pnpm start

Hope it will help for debugging.

@valentinpalkovic I have make sure that, in the Angular UI lib, angular is a peerDependency and with a pnpm why @angular/core there is only 1 version of angular installed 16.2.12.

I have no error in dev it's only with the builded storybook. (You can try pnpm storybook)

@valentinpalkovic, are this exemple good for debugging what is the error ?

@elvinachang
Copy link

Having the same issue here, any workarounds on this?
Using storybook 8.2.9 with angular 16.
It def look like its the build of storybook that is giving this error for components used within stories.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.