Storybook patterns for Angular components including story structure, service mocking with implements Pick, and rxResource mocking. Use when creating or modifying story files (.stories.ts).
Storybook patterns for Angular components.
Use satisfies Meta<> and satisfies StoryObj<> for type safety.
import { type Meta, type StoryObj } from '@storybook/angular';
import { CardDetailsComponent } from './card-details.component';
const meta = {
component: CardDetailsComponent,
parameters: {
a11y: { test: 'todo' },
},
} satisfies Meta<CardDetailsComponent>;
export default meta;
export const Default = {
args: {
card: {
id: '1',
term: 'Example Term',
definition: 'This is an example definition.',
tags: ['tag1', 'tag2'],
},
},
} satisfies StoryObj<CardDetailsComponent>;
export const WithoutTags = {
args: {
card: {
id: '2',
term: 'Simple Term',
definition: 'No tags on this one.',
tags: [],
},
},
} satisfies StoryObj<CardDetailsComponent>;
Mock services with type safety using .
implements Pickimport { applicationConfig } from '@storybook/angular';
import { type Meta, type StoryObj } from '@storybook/angular';
import { action } from 'storybook/actions';
import { CardsService } from '../cards.service';
import { CardsListComponent } from './cards-list.component';
class MockCardsService {
removeCard = action('removeCard');
updateCard = action('updateCard');
}
const meta = {
component: CardsListComponent,
args: {
cards: [...],
listAriaLabel: 'Cards List',
},
decorators: [
applicationConfig({
providers: [{ provide: CardsService, useClass: MockCardsService }],
}),
],
} satisfies Meta<CardsListComponent>;
export default meta;
For services that return rxResource, use resource for mocking.
import { resource } from '@angular/core';
class MockCardsService implements Pick<CardsService, 'getCards' | 'updateCard'> {
getCards() {
return resource({
loader: async () => [{ id: '1', term: 'Mock Card', definition: 'Mock definition', tags: [] }] as DbCard[],
});
}
updateCard = action('updateCard') as CardsService['updateCard'];
}
For directives or complex templates, use custom render.
const meta = {
component: ButtonDirective,
render: args => ({
template: `
<button appButton appButtonType="${args.type}">
Button Text
</button>
`,
props: args,
}),
} satisfies Meta<ButtonDirective>;
import { provideRouter, withHashLocation } from '@angular/router';
import { routes } from 'src/app/app.routes';
const meta = {
component: LayoutComponent,
parameters: { layout: 'fullscreen' },
render: () => ({
applicationConfig: {
providers: [provideRouter(routes, withHashLocation())],
},
template: `
<app-layout>
<p>Page content</p>
</app-layout>
`,
}),
} satisfies Meta<LayoutComponent>;
export const Default = {} satisfies StoryObj<CardsListComponent>;
export const Loading = {
args: {
isLoading: true,
cards: [],
},
} satisfies StoryObj<CardsListComponent>;
export const LoadingError = {
args: {
loadingError: new Error('Failed to load cards.'),
cards: [],
},
} satisfies StoryObj<CardsListComponent>;
export const Empty = {
args: {
cards: [],
},
} satisfies StoryObj<CardsListComponent>;
Pass args directly to component with custom render.
const meta = {
component: MyComponent,
render: args => ({
props: args,
template: `
<app-my-component
${argsToTemplate(args)}
[extraProp]="true"
></app-my-component>
`,
}),
} satisfies Meta<MyComponent>;
parameters or argssrc/app/cards/cards-list/cards-list.stories.ts - Basic story with service mocksrc/app/cards/search-cards/search-cards.stories.ts - rxResource mock patternsrc/app/ui/button/button.stories.ts - Directive with custom rendersrc/app/layout/layout.stories.ts - Story with router providersrc/app/cards/card-details/card-details.stories.ts - Multiple story states