Как использовать входные сигналы Angular с Storybook

Я пытаюсь настроить Storybook 8.0.8 для работы с angular 17.3. Я использую сигнал Angular input() в компонентах и ​​сталкиваюсь с интересной ошибкой: аргументы историй из сборника рассказов также хотят, чтобы тип аргумента был сигналом. Я мог бы попытаться получить контекст инъекции, доступный в истории, но мне это не кажется «хорошим способом сделать что-то».

Я надеюсь на чистый, встроенный способ предоставления аргументов в виде буквальных значений, как это было раньше. Кто-нибудь сталкивался с такой же проблемой и нашел достойное решение?

Вот простой угловой компонент и история сборника рассказов, которая повторяет мою проблему.

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

Напоминаем, что при использовании любых функций создания signal() предполагается, что контекст внедрения находится в области видимости. Вызов signal() без контекста инъекции приводит к

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

🤔 А знаете ли вы, что...
Angular предоставляет множество встроенных директив для управления отображением элементов на веб-странице.


4
984
2

Ответы:

Я нашел обходной путь к своей проблеме. Storybook просто использует определение класса компонента в качестве ожидаемого типа для аргументов историй, но, похоже, он не привязывает значения напрямую к экземпляру компонента. Работая над системой типов TypeScripts со следующим, она становится компилируемой, и Storybook отлично отображает компонент.

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

На мой взгляд, это все еще отвратительно, но это работает


Решено

В Storybook 8.0.8 проблема в том, что сборник рассказов может использовать несигнальные свойства в качестве аргументов, но ожидает, что мы предоставим сигнальное. Внутри Storybook использует TransformEventType, который преобразует любой EventEmitter в обратный вызов. Т.е. мы можем предоставить обратный вызов для свойства EventEmitter. К сожалению, внутренне SB не преобразует тип входного сигнала в удерживаемое значение сигнала.

Поэтому я написал этого временного помощника:

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;

Затем вы можете использовать его следующим образом:

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