型安全にCSSのオブジェクトを書きたいというだけならNgStyleとcsstype
を使うだけでもよさそうだ。
emotionを使うことによる利点は、
くらいなものか。
今日の境界遊び。CSS in JSをAngularでやりたかった。 常識のある方は真似しないほうがよい。
今回使ったのは https://emotion.sh/.
Angularで時々困るのはstylesの中にデータバインディングを置きたいケース。 たとえば、フォームの入力に応じて動的にフォントサイズを変えるようなケースを考える。
emotionの css
関数は、与えたCSSスタイルシートをシリアライズしてユニークなCSSクラス名に変換してくれる。
AngularのコンポーネントはHTML要素と1:1に対応するので、 [className]
プロパティにバインディングすればemotionで生成したクラスを適用できる。
helloClassName$
プロパティは、フォントサイズに応じたCSSスタイルシートをCSSクラス名に変換したObservableである。
helloClassName$ = this.form.valueChanges.pipe( map(({ fontSize }) => css({ fontSize })) );
これをテンプレート中で次のように使えば、emotionによって動的に生成されたクラスを任意の要素に適用できる。
<hello [className]="helloClassName$ | async" [name]="name"></hello>
ところで、Angularの CommonModule
(@angular/common) は、 NgClass
と NgStyle
という2つのディレクティブを提供している。
https://angular.io/api/common/NgStyle
https://angular.io/api/common/NgClass
className
を使わずとも、次のように書くこともできる。NgClass
は文字列以外にも文字列の配列やオブジェクトを受け取れる以外には、本質的にclassName
と何も違いはない。
<hello [ngClass]="helloClassName$ | async" [name]="name"></hello>
NgStyle
を使う場合は、emotionではなく生のスタイルシートっぽいオブジェクトを渡すことになる。
本来AngularだけでCSS-in-JSやろうとするとこのAPIになるわけだが、emotionだとcss
関数の引数オブジェクトにTypeScript型定義もあるし嬉しいのでは?という目論見がある。
あとemotionなら同じスタイルなら同じクラスになり、キャッシュの仕組みが強いっぽいので、パフォーマンス良くなるかもしれない。
https://emotion.sh/docs/typescript
helloStyle$ = this.form.valueChanges.pipe( map(({ fontSize }) => ({ fontSize: `${fontSize}px` })) ); <hello [ngStyle]="helloStyle$ | async" [name]="name"></hello>
すべて同じ動きとなるので好みで選べばよいが、個人的にはReactとの対称性を考えて[className]
でよいのでは?と感じる。
実際に動くサンプルは次の通り。
Reactの場合、classNameはHTML要素に対応するコンポーネントにしか使えないが、Angularの場合すべてのコンポーネントはHTML要素に対応付けられるので、テンプレート中で親からclassNameプロパティにバインディングするだけで子コンポーネント側でなにもしなくてもよいのは、比較的楽だなと思った。 しかしemotionで一番やりたいstyled-componentがReactしか使えないので、これをどうにかしてみたい。