これは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を書き直していきます。
@Component({
selector: 'app-menu',
templateUrl: 'menu.html',
styleUrls: ['menu.css'],
hostDirectives: [CdkMenu],
})
export class Menu {}
@Component({
selector: 'app-menu-item',
templateUrl: 'menu-item.html',
styleUrls: ['menu-item.css'],
hostDirectives: [CdkMenuItem],
})
export class MenuItem {}
これでも十分ですが、折角なのでトリガーのprefixをappに統一します。
@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さんです!