angularngrxstorybookangular-storybook

Storybook: Inject a mocked provider/service at component level


I have an Angular component that has a component level provider.

@Component({
  selector: 'storybook-di-component',
  templateUrl: './di.component.html',
  providers: [{ provide: TEST_TOKEN, useValue: 123 }],
})
export class DiComponent {
  @Input()
  title: string;

  constructor(
    protected injector: Injector,
    protected elRef: ElementRef,
    @Inject(TEST_TOKEN) protected testToken: number
  ) {}

In such cases, how do I get storybook to inject a different provider so that I can provide an alternate/mock service? For example, in the DiComponent above, what if I wanted to inject { provide: TEST_TOKEN, useValue: 456 } instead?

The real world use case is that I'm using ngrx/component-store and need to provide a dummy, prepopulated store for the component.

(Additional info:) Injecting it at module level (like below) doesn't work as the component still goes and creates it's own instance of the provider:

moduleMetadata: {
        providers: [
            { provide: StateStore, useValue: stateStore }
        ],
        imports: [...],
    },

Solution

  • I ended up solving this problem using a low tech approach.

    1. Subclass the component in the .stories.ts file
    2. In the sub class, declare the constructor to inject everything other than the service needing to be mocked.
    3. Call super(...) with the injected services + the mocked service.

    Given that my actual need was to inject a mock of the ngrx/component-store my stories.ts has something like the following now:

    const stateStore = new RecordSuggestionsPageStore(mockSuggestionsService);
    stateStore.setState(() => ({
        selectableRecords: [
            { name: "John Legend", recordType: "employee" },
            { name: "Allan Davies", recordType: "employee" },
        ]
    }));
    
    @Component({
        selector: "myapp-suggested-records-page",
        templateUrl: "./suggested-records-page.component.html",
        styleUrls: ["./suggested-records-page.component.scss"]
    })
    class SuggestedRecordsPageComponentStubbed extends SuggestedRecordsPageComponent {
    
        constructor(router: Router) {
            super(stateStore, router);
        }
    }