5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Directive composition API + Angular CDKでお手軽自作Menuの実装

Last updated at Posted at 2022-12-07

これはAngular Advent Calendar 2022の8日目の記事です。

Angular v15で追加されたDirective composition APIがAngular CDKとすごく相性が良さそうなので、説明ついでにCDKの布教活動をしたいと思います。

Directive composition APIってなに?

The directive composition API lets you apply directives to a component's host element from within the component.

公式にはこんな感じで書いてあります。ざっくりと「Component側の定義でDirectiveをホスト要素に追加できるよ。」といった感じです。

ComponentにDirectiveを追加する場合に、今まではこんな感じで書いていました。

<app-cmp appDir></app-cmp>

Directive composition APIではComponentデコレータで追加出来ます。

@Component({
  selector: 'app-cmp',
  template: 'app-cmp.html',
  hostDirectives: [AppDir]
})
export class AppCmp {}

使い所は多くないかもしれませんが、ComponentとDirectiveをセットで利用するケースなどに便利です。
とはいえイメージがしづらいので、CdkMenuを題材に利用ケースを考えてみましょう。

CdkMenuを使う

CdkMenuはv14で追加されました。OverlayやPortalを使わずA11yまで考えてくれてすごく便利です。しかし、Directiveを提供しているだけなので、実装するとDirectiveだらけになります。

<button [cdkMenuTriggerFor]="menu">Click me!</button>

<ng-template #menu>
  <div cdkMenu>
    <button cdkMenuItem>Item 1</button>
    <button cdkMenuItem>Item 2</button>
  </div>
</ng-template>

もちろんこのままで使えますが、Menuは様々なところで再利用したいので共通化するために、「常に同じclassを当てる」か「Componentとセットで使う」必要がありますが、それぞれ微妙にめんどくさいですよね。

<button [cdkMenuTriggerFor]="menu">Click me!</button>

<ng-template #menu>
  <div cdkMenu>
   <!-- classを追加し忘れそう -->
    <button class="custom-menu-item" cdkMenuItem>Item 1</button>
    <!-- 毎回cdkMenuItemを追加するのめんどくさい --> 
    <app-menu-item cdkMenuItem>Item 2</app-menu-item>
  </div>
</ng-template>

この問題を解決してくれるのがDirective composition APIです。Directive compostion APIを使って書き直してみましょう。

CdkMenu with Directive composition API

まずはCdkMenuとCdkMenuItemを書き直していきます。

menu.component.ts
@Component({
  selector: 'app-menu',
  templateUrl: 'menu.html',
  styleUrls: ['menu.css'],
  hostDirectives: [CdkMenu],
})
export class Menu {}
menu-item.component.ts
@Component({
  selector: 'app-menu-item',
  templateUrl: 'menu-item.html',
  styleUrls: ['menu-item.css'],
  hostDirectives: [CdkMenuItem],
})
export class MenuItem {}

これでも十分ですが、折角なのでトリガーのprefixをappに統一します。

menu-trigger.directive.ts
@Directive({
  selector: '[appMenuTriggerFor]',
  hostDirectives: [
    {
      directive: CdkMenuTrigger,
      // inputsを利用することで@Inputを任意の名前で利用できます
      inputs: ['cdkMenuTriggerFor: appMenuTriggerFor'],
    },
  ],
})
export class MenuTriggerDirective {}

ここまでやるとHTMLはスッキリします。

<button [appMenuTriggerFor]="menu">Click me!</button>

<ng-template #menu>
  <app-menu>
    <app-menu-item>Item 1</app-menu-item>
    <app-menu-item>Item 2</app-menu-item>
  </app-menu>
</ng-template>

なんかよく見る形になってませんか?これだけで再利用しやすい自作Menuが実装できちゃいます。

最後に

Angular CDKはDirectiveで提供されている機能がたくさんあるのでDirective composition APIと上手く組合わせることでMenu以外もお手軽に作れそうな気がしているので個人的には楽しみしかないです。

Angular CDKには他にも色々な機能がありますが、これを機にまずはMenuだけでもAngular CDKを使ってみませんか?

サンプルも作っているので是非参考にしてください。

明日は@ocknamoさんです!

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?