question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Angular 12 library with storybook: Cannot access 'component' before initialization

See original GitHub issue

Describe the bug

This bug only happens when setting the path in tsconfig.json to the source files of the library instead the dist file

To make an Angular library works smoothly with the app live reload, I followed this SO answer https://stackoverflow.com/a/65866136/1015648, in short, I needed to replace the default paths of the library in tsconfig.json with the source library files instead of usin the dist path.

When I added the storybook, it builds successfully but throws a runtime error:

bootstrap:27 Uncaught ReferenceError: Cannot access 'CarouselNav' before initialization
    at Object../projects/ng-gallery/src/lib/carousel/carousel-nav/carousel-nav.component.ts (carousel-nav.component.ts:26)
    at __webpack_require__ (bootstrap:24)
    at fn (hot module replacement:61)
    at Object../projects/ng-gallery/src/lib/carousel/carousel.module.ts (carousel.model.ts:37)
    at __webpack_require__ (bootstrap:24)
    at fn (hot module replacement:61)
    at Object../projects/ng-gallery/src/lib/carousel/index.ts (centralised-slider.ts:24)
    at __webpack_require__ (bootstrap:24)
    at fn (hot module replacement:61)
    at Object../projects/ng-gallery/src/lib/components/gallery.component.ts (gallery-thumbs.component.ts:15)

The error is thrown because of injecting the parent component in a directive which works fine outside the storybook

@Directive({
  selector: '[carouselNavNextButton]'
})
export class CarouselNavNextButton {
  constructor(@Inject(forwardRef(() => CarouselNav)) public parent: CarouselNav) {
  }
}
{
  "compilerOptions": {
    "paths": {
      "ng-gallery": [
        "projects/ng-gallery/src/lib"
      ],
      "ng-gallery/*": [
        "projects/ng-gallery/src/lib/*"
      ]
    }
}

To Reproduce

ng g lib my-lib

Go to projects /my-lib/src/lib directory and create index.ts file which exports lib components that are meant to be publicly available

Edit the projects /my-lib/src/public-api.ts file in a way it exports all from the previously created index.ts file, e.g.: export * from './lib/index';

{
  "compilerOptions": {
    "paths": {
      "my-lib": [
        "projects/my-lib/src/lib"
      ],
      "my-lib/*": [
        "projects/my-lib/src/lib/*"
      ]
    }
}

System

Environment Info:

  System:
    OS: macOS 11.5
    CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
  Binaries:
    Node: 14.16.1 - /usr/local/bin/node
    npm: 6.14.13 - /usr/local/bin/npm
  Browsers:
    Chrome: 92.0.4515.107
    Edge: 92.0.902.62
    Safari: 14.1.2
  npmPackages:
    @storybook/addon-actions: ^6.4.0-alpha.22 => 6.4.0-alpha.22 
    @storybook/addon-essentials: ^6.4.0-alpha.22 => 6.4.0-alpha.22 
    @storybook/addon-links: ^6.4.0-alpha.22 => 6.4.0-alpha.22 
    @storybook/angular: ^6.4.0-alpha.22 => 6.4.0-alpha.22 
    @storybook/builder-webpack5: ^6.4.0-alpha.22 => 6.4.0-alpha.22 
    @storybook/manager-webpack5: ^6.4.0-alpha.22 => 6.4.0-alpha.22 

Additional context

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:8
  • Comments:10 (3 by maintainers)

github_iconTop GitHub Comments

5reactions
Marklbcommented, Nov 11, 2021

@SarcevicAntonio Thanks for the simple repro. As @salmoro said, the problem is the circular dependency and I also would most likely fix the problem with InjectionToken’s.

It has been a while since I have run into this problem and I don’t remember why Angular’s compiler allows this, but the circular dependency is obvious in that small repro. I ran into many of those circular dependencies scattered among many files when pulling parts of my app into a library and since then I have been very careful to avoid dealing with that again. If you were to build the library with ng-packagr then you would see warnings telling you where the circular dependency is. Surprisingly, the built library seems to actually work for that one. Normally when I ignore those warnings, my apps that import the package have various errors that don’t clearly point out the problem, which is why this issue isn’t obvious that the circular dependency is the problem.

I forked your repro in a Stackblitz WebContainer, with one way I may fix the problem. https://stackblitz.com/edit/github-knujip?preset=node

For reference, and in case the WebContainer doesn’t work, I will explain what I did.

In ChildComponent there are references to LibComponent and in LibComponent there are references to ChildComponent, which is the circular problem, because which one should be first?

First I created a new file projects/lib/src/lib/container-accessor.ts and defined an InjectionToken. I also defined an interface for the instance being injected to implement. That isn’t necessary, but to avoid typing the injected instance as any or potentially hitting the circular dependency again. It also just abstracts the implementation, to where you could swap the container to anything else implementing the interface without updating your child components.

// projects/lib/src/lib/container-accessor.ts
import { InjectionToken } from '@angular/core'

/**
 * Implemented by all components that can contain components implementing `ContainerChild`.
 */
export interface ContainerAccessor {
  someFunction(): void
}

export const CONTAINER_ACCESSOR = new InjectionToken<ContainerAccessor>('ContainerAccessor')

I made LibComponent provide itself as token CONTAINER_ACCESSOR. It is still in the Injector, like it was before where it is provided as it’s class, but now it is also provided by the token CONTAINER_ACCESSOR. The new token CONTAINER_ACCESSOR isn’t defined by either class, so there isn’t a circular dependency.

import { ContainerAccessor, CONTAINER_ACCESSOR } from './container-accessor';
...
@Component({
  selector: 'lib-parent',
  ...,
  providers: [
    { provide: CONTAINER_ACCESSOR, useExisting: forwardRef(() => LibComponent) },
  ],
})
export class LibComponent implements AfterViewInit, ContainerAccessor {
  ...
}

When providing with an InjectionToken, I don’t think there is a way to just use types. So, @Inject will be used to tell the Injector which token to search for when injecting. (I also removed the redundant instance variable declaration, because adding public, private, or protected makes constructor args instance variables. Since @Optional was used, I made the type more accurate and added ?. Then readonly is just my preference, because I don’t want anyone to reassign a variable that was injected.)

import { CONTAINER_ACCESSOR, ContainerAccessor } from './container-accessor';
...
export class ChildComponent implements OnInit {
  constructor(
    @Optional() @Inject(CONTAINER_ACCESSOR) private readonly parent?: ContainerAccessor
  ) { }
}

As for why Storybook’s build doesn’t work, but Angular’s does, I would need to dig through what all is being done. Storybook merges necessary parts of Angular’s Webpack config, but I don’t know if it is something in Storybook’s config that isn’t allowing that circular dependency or something that isn’t being used from Angular’s config. Either way, if you don’t avoid the circular dependency, you will probably run into problems later on when the project grows and Angular’s builder can’t figure out how to deal with the circular dependency either.

3reactions
salmorocommented, Sep 24, 2021

I have experienced similar errors when I had circular dependencies in my code. I’d get the same runtime error as mentioned by @MurhafSousli and the stories for which those errors were thrown did not show up in the storybook dashboard. After removing the circular dependencies (usually via injection tokens) the errors were gone and the stories appeared.

The annoying part of this is that it’s inconsistent with the angular build which works well in-spite of these circular dependencies.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Angular: 7.2.1 ES6 class ReferenceError : Cannot access 'X ...
I was getting this error due to a circular dependency, like. A injected with B; B injected with C; C injected with A....
Read more >
Cannot access 'XXX' before initialization - r/angular ... - Reddit
Hello I get this error message when I try to import a component into another component. The component in question is the BigChartComponent....
Read more >
ReferenceError: Cannot access before initialization in JS
The "Cannot access before initialization" error occurs when a variable declared using let or const is accessed before it was initialized in the...
Read more >
Assemble a composite component - Storybook Tutorials
Learn how to develop UIs with components and design systems. Our in-depth frontend guides are created by Storybook maintainers and peer-reviewed by the...
Read more >
How to Build a Component Library with Angular and Storybook
In this article, I first describe how to build a component library with Angular CLI. After the library is done, I write some...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found