typescript - Hur använder jag Angular-inputsignaler med Storybook


Jag försöker sätta upp Storybook 8.0.8 för att fungera med Angular 17.3. Jag använder Angular input()-signal i komponenter, och jag stöter på ett intressant problem där storybook-berättelsernas args också vill att argumenttypen ska vara en signal. Jag skulle kunna försöka få injektionskontexten tillgänglig i berättelsen, men detta verkar inte för mig som "ett bra sätt att göra saker på".

Jag hoppas på ett rent, inline-sätt att tillhandahålla mina argument som bokstavliga värden som de brukade vara. Har någon där ute stött på samma problem och hittat en bra lösning?

Här är en enkel Angular-komponent och Storybook-berättelse som replikerar mitt problem

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);
}

Påminnelse om att användning av alla signal()-skapandefunktioner förväntar sig att ett injektionskontext är i scope. Att anropa signal() utan ett injektionskontext ger

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

Svar #1

Jag har kommit på en lösning på mitt eget problem. Storybook använder bara komponentens klassdefinition som den förväntade typen för storyns argument, men det verkar inte binda värdena direkt till komponentens instans. Genom att arbeta runt TypeScripts typ-system med följande blir det kompilerbart och Storybook renderar komponenten utan problem.

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

Enligt min åsikt är detta fortfarande äckligt men det fungerar

Svar #2

[REDIGERA]: signalens skrivfunktion verkar ha förbättrats från och med v8.5

Storybook 8.0.8, problemet är att storybook kan använda icke-signal-egenskaper som argument men förväntar sig att vi ska tillhandahålla en signal. Internt använder Storybook TransformEventType som konverterar vilken EventEmitter som helst till en callback. D.v.s. vi kan tillhandahålla en callback för en EventEmitter-egenskap. Tyvärr, internt, omvandlar inte SB typen av signalingångar till det hållna värdet av signalen.

Jag skrev därför denna tillfälliga hjälpare:

storybook.helper.ts

export function toArgs<Component>(
    args: Partial<TransformSignalInputType<TransformEventType<Component>>>
): TransformEventType<Component> {
    return args as unknown as TransformEventType<Component>;
}

/** Convert event emitter to callback for storybook */
type TransformEventType<T> = {
    [K in keyof T]: T[K] extends EventEmitter<infer E> ? (e: E) => void : T[K];
};

/** Convert any input signal into the held type of the signal */
type TransformSignalInputType<T> = {
    [K in keyof T]: TransformInputType<T[K]>;
};

import { InputSignalWithTransform, InputSignal } from '@angular/core';

// Type to extract the type from InputSignal or InputSignalWithTransform
type TransformInputType<T> =
    T extends InputSignalWithTransform<infer U, any>
        ? U
        : T extends InputSignal<infer U>
          ? U
          : T;

Du kan sedan använda det på följande sätt:

const meta: Meta<NoteComponent> = {
  title: 'Shared/Note',
  component: NoteComponent,
  tags: ['autodocs'],
  args: toArgs<NoteComponent>({
    maxChars: 200
  })
};

Relaterade artiklar