Search code examples
angulartypescriptstorybookangular-signals

How do I use Angular input signals with Storybook


I am trying to setup Storybook 8.0.8 to work with angular 17.3. I am using Angular input() signal in components, and I am running into an interesting gotcha where the storybook stories args also want the argument type to be a signal. I could try and get the injection context available in the story, but this doesn't come off to me as "a good way to do things".

I'm hoping for a clean, inline way to provide my args as literal values as they used to be. Anyone out there ran into this same problem and found a decent solution?

Here's a simple angular component and storybook story that replicates my issue

const meta: Meta<NoteComponent> = {
  title: 'Shared/Note',
  component: NoteComponent,
  tags: ['autodocs'],
  args: {
    maxChars: 200 // Error: Type 'number' is not assignable to type 'InputSignal<number>'.
  }
};

export default meta;
type Story = StoryObj<NoteComponent>;

export const Primary: Story = {
};
@Component({
  selector: 'app-note',
  template: 'A note with {{maxChars()}} maximum characters',
  standalone: true,
})
export class NoteComponent {
  maxChars = input<number>(300);
}

Reminder that using any signal() creation functions expects an injection context to be in scope. Calling signal() without an injection context produces

NG0203: inputFunction() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with runInInjectionContext


Solution

  • I've figured out a workaround to my own issue. Storybook just uses the component's class definition as the expected type for the stories args, but it doesn't seem to bind the values directly to the instance of the component. By working around TypeScripts type system with the following, it becomes compilable and Storybook renders the component just fine.

    const meta: Meta<NoteComponent> = {
      title: 'Shared/Note',
      component: NoteComponent,
      tags: ['autodocs'],
      args: {
        maxChars: 200 as unknown as InputSignal<number>
      }
    };
    

    In my opinion, this is still gross but it works