Я пытаюсь настроить 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 предоставляет множество встроенных директив для управления отображением элементов на веб-странице.
Я нашел обходной путь к своей проблеме. 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
})
};