Angular で Storybook を使うための手順まとめ

Angular で Storybook を使うための手順まとめ

Angular で Storybook を使いたい

Angular で作られているプロジェクトに、Storybook を導入する手順をまとめます。

上記ページの手順に従えばOKです。

Storybook とは

Storybook とは UIコンポーネントのカタログを作るためのツールです。各コンポーネントの状態に応じた表示が確認できるようになります。

アプリケーションに依存しない形で実行されるため、これを用いることでコンポーネントを独立した形で開発ができるようになり、コンポーネントの再利用性が高まります。

React や Vue と同じように Angular でも使えるので、導入手順を以下に記します。

導入手順サンプル

以下導入手順のサンプルです。上記URL参考。

プロジェクト作成

プロジェクトを AngularCLI で作成し、これに Storybook を導入します。プロジェクトの名前は angular-storybook-app とします。

$ ng new angular-storybook-app
$ cd angular-storybook-app

プロジェクト作成時の設定は適当でいいです。

@storybook/cli をインストールして自動セットアップ

Storybook にも Angular のように CLI が用意されているのでこれをインストールします。インストールと同時にプロジェクトに Storybook が使えるようにセットアップします。

$ npx -p @storybook/cli sb init --type angular

このコマンド1つでお手軽セットアップ完了です。

動かしてみる

セットアップが完了すると、package.json に起動用スクリプトが追加されています。

$ npm run storybook

起動するとブラウザで Storybook が立ち上がります。

起動後はコードを編集すると画面も自動でリロードされます。ng serve と同じような感じです。

導入手順は以上の通りです。

Angular での Storybook の使い方

導入が済んだので基本的な使い方です。

/src/stories/index.stories.ts が Storybook にコンポーネントを追加するためコードです。ここにコンポーネントを追加すると、Storybook のページが更新されます。

Storybook の記述方法

デフォルトで生成される

storiesOf('Welcome', module).add('to Storybook', () => ({
  component: Welcome,
  props: {},
}));

storiesOf() が Storybook の左のツリーの項目を定義する関数です。ここでは単一のコンポーネントを指定します。

単一のコンポーネントに対して任意の数の状態(Story)を add() で追加していきます。

上記例では Welcome コンポーネント(Storybookで用意されてるサンプルコンポーネント)に対して1つの状態を定義し、Storybook に追加しています。

@Input, @Output の指定の仕方

これも Storybook のサンプルであらかじめ記述されているデータからの引用です。

storiesOf('Button', module)
  .add('with text', () => ({
    component: Button,
    props: {
      text: 'Hello Button',
    },
  }))
  .add(
    'with some emoji',
    () => ({
      component: Button,
      props: {
        text: '😀 😎 👍 💯',
      },
    }),
    { notes: 'My notes on a button with emojis' }
  )
  .add(
    'with some emoji and action',
    () => ({
      component: Button,
      props: {
        text: '😀 😎 👍 💯',
        onClick: () => action('This was clicked OMG'),
      },
    }),
    { notes: 'My notes on a button with emojis' }
  );

Button コンポーネントは、@Input@Output でそれぞれ textonClick を受け取ります。ボタンのテキストとクリック時のイベントですね。

これらのパラメータは add() の引数の中の props のオブジェクトで指定します。onClick で渡しているイベント action() は Storybook 画面下部の Actions エリアにログをいい感じに出力してくれるコールバック関数を生成してくれます。

こんな感じで出力されます。

単純なコンポーネントであればこれでよいのですが、複数のモジュールやコンポーネントに依存関係を持つ場合には以下のように moduleMetadata を指定します。Angular と同じように書けばOKです。

storiesOf('Welcome', module).add('to Storybook', () => ({
    component: Welcome,
    props: {},
    moduleMetadata: {
      imports: [],
      schemas: [],
      declarations: [],
      providers: []
    }
  }));

以下、いくつかの複雑なコンポーネントのパターンを見ていきます。

別のコンポーネントに依存するコンポーネントの表示方法

コンポーネントAがコンポーネントBに依存する場合、例えば以下のようなコンポーネントを表示するにはどうすればよいでしょうか。

@Component({
  selector: 'a-component',
  template: `<p>A → <b-component></b-component></p>`,
})
export class AComponent { }

@Component({
  selector: 'b-component',
  template: `B`,
})
export class BComponent { }

// 単純に A → B と表示するだけです。

単純に AComponent を Storybook に追加すると以下のようなエラーで怒られます。

Template parse errors: 'b-component' is not a known element: 1. If 'b-component' is an Angular component, then verify that it is part of this module. 2. If 'b-component' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("<p>A → [ERROR ->]<b-component></b-component></p>"): ng:///DynamicModule/AComponent.html@0:7

この場合は依存するコンポーネントを declarations に追加してやる必要があります。

storiesOf('ABComponent', module).add('sample', () => ({
  component: AComponent,
  moduleMetadata: {
    imports: [],
    schemas: [],
    declarations: [BComponent],
    providers: []
  }
}));

これで表示されました。

ng-content で直接入れ子になっているコンポーネント

ng-content タグを使えば、コンポーネントのタグに囲まれた子要素を取得してテンプレート内に展開できます。以下のようなコンポーネントを考えます。

@Component({
  selector: 'error-text',
  template: `<p style="color: red;"><ng-content></ng-content></p>`,
})
export class ErrorTextComponent { }

子要素を受け取って赤字にするだけのコンポーネントです。

Storybook 内で直接パラメータから子要素は指定はできません。したがってコンポーネントを指定するのではなく、直接templateのタグを記述して実現します。

storiesOf('ErrorTextComponent', module).add('sample', () => ({
  template: `<error-text>Error</error-text>`,
  moduleMetadata: {
    declarations: [ErrorTextComponent],
  }
}));

template で直接 Angular コンポーネントのテンプレートを書くことができます。ここで使われるコンポーネントは declarations で設定しておきましょう。

RouterLink を使うコンポーネント

@Component({
  selector: 'my-link',
  template: `<a routerLink="/hoge">MyLink</a>`,
})
export class MyLinkComponent { }

routerLink を使っているコンポーネントの場合は、RouterModule を用意しなければリンクになりません。しかしルーター本体は不要なのでテスト用のモジュールで代用します。

storiesOf('MyLinkComponent', module).add('sample', () => ({
  component: MyLinkComponent,
  moduleMetadata: {
    imports: [RouterTestingModule],
  }
}));

ルーターモジュールがインポートされていないとリンクになりませんでした。

そのほか依存する Service や Module があれば都度追加すればだいたい動きます。基本的にはテスト用のモックを用意したほうがよいと思いました。

以上。

Angularカテゴリの最新記事