【Angular】コンポーネント要素にclassを付与するやり方

# はじめに
Angularではhtmlをコンポーネントに切り出すと、作成したコンポーネントも一つのDOMとなります。
そのためコンポーネントに切り出す前と比べてDOMの階層が1階層増えてしまいます。このときに起こる問題として、例えばDOMの親子関係を前提としたCSSを適用させていた場合に元のDOMの階層と変わってしまうがために親子関係が崩れ、適用したいCSSのスタイルが当たらなくなってしまうことがあります。
ここでは切り出したコンポーネント要素自体にclassや属性を付与することでそれを回避した方法を記します。

# 前提
Angular 9.1.0を前提としています。今後のバージョン次第で記述方法や動作が変わる可能性があります。

# 具体的にどんなときに起こる問題か
例えば以下のコードがあるとします。

```html:sample.html
<div class="form">
  <input type="text"/>
  <div class="select">
    <select>
      <option>A</option>
      <option>B</option>
      <option>C</option>
    </select>
  </div>
  <button>追加</button>
</div>
```

```css:sample.css
.form > .select {
  padding: 5px;
  width: 100%;
}
```

CSSではformクラスの直下にあるselectクラスのpaddingが5px, 横幅が100%になるように記述しています。

上記の場合、生成されるDOM階層は以下のようになります。

```
div(.form)
 |-- input
 |-- div(.select)
   |-- select
     |-- option
     |-- option
     |-- option
 |-button
```

このとき、selectクラスのdiv要素部分をコンポーネントに切り出すとします。

```html:select.component.html
<div class="select">
  <select>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
</div>
```

```ts:select.component.ts
@Component({
  selector: 'app-select',
  templateUrl: './select.component.html',
  styleUrls: []
})
export class SelectComponent {
}
```

```html:sample.html
<div class="form">
  <input type="text"/>
  <app-select></app-select>
  <button>追加</button>
</div>
```

上記のように`app-select`というコンポーネントに切り出しました。
この場合生成されるDOMは以下のような階層構造になります。

```
div(.form)
 |-- input
 |-- app-select
   |-- div(.select)
     |-- select
       |-- option
       |-- option
       |-- option
 |-button
```

元々の階層構造では`div.form > div.select`という構造でしたが、コンポーネントに切り出したことで元のDOMの階層構造と変わってしまい、`div.form > app-select > div.select`という構造になります。
今回のように**親子関係のDOMを前提としたCSSがある場合にそれを崩すようなコンポーネントの切り方をしてしまうとCSSスタイルが適用されず、表示が崩れてしまう**現象が起こってしまいます。

# どう解決するか
解決方法としてはいくつかあるかと思いますが、今回はなるべくCSSを触らずに回避したいと思います。（エンジニアはあまり触りたくないですよね？（偏見））
`app-select`要素自体にクラスを適用できれば`.form > .select`の親子関係は維持できるため、CSSスタイルを適用できそうです。
今回はその方法で回避します。

つまり、この記事ではコンポーネント要素自体にクラスを付与させる方法を紹介します。
静的にクラスを付与させる方法と動的にクラスを付与させる方法の2通りを紹介します。

# コンポーネント要素に静的にクラスを付与させる方法
`@Component`にはhostオプションを定義することができます。
hostオプションは`@Directive`アノテーションから継承しており、プロパティや属性、イベントをkey-valueの形式で与えることができます。

```ts
host: {
    [key: string]: string;
}
```

- [Angular 日本語ドキュメンテーション - Component](https://angular.jp/api/core/Component)
- [Angular 日本語ドキュメンテーション - Directive#host](https://angular.jp/api/core/Directive#host)

例えば先程の`app-select`にクラスを付与する場合、以下のように書くことができます。

```ts:select.component.ts
@Component({
  selector: 'app-select',
  templateUrl: './select.component.html',
  styleUrls: [],
  host: {
    class: 'select' // 付与したいclassをスペース区切りで指定する
  }
})
export class SelectComponent {
}
```

これにより、`app-select`にselectクラスを付与することができました。

# コンポーネントルートに動的にクラスを付与させる方法
`@HostBinding`アノテーションを利用します。役割は先程の`@Component`のhostオプションとほぼ同じです。
`@HostBinding`はhostPropertyNameオプションだけを持ちます。hostPropertyNameにはコンポーネント要素に付与したい属性を指定できます。
指定の仕方は`<属性>.<属性名>`で指定します。

- [Angular 日本語ドキュメンテーション - HostBinding](https://angular.jp/api/core/HostBinding)

例えば先程の`app-select`にクラスを付与する場合、以下のように書くことができます。

```ts:select.component.ts
@Component({
  selector: 'app-select',
  templateUrl: './select.component.html',
  styleUrls: []
})
export class SelectComponent {
  @HostBinding('class.select') selectClass: boolean = true;  // trueの場合付与される
}
```

`@HostBinding`は変数に付与できるため、クラスの付け外しを以下のように動的に行うことができます。

```ts:select.component.ts
@Component({
  selector: 'app-select',
  templateUrl: './select.component.html',
  styleUrls: []
})
export class SelectComponent {
  @HostBinding('class.select') selectClass: boolean = true;

  @Input
  set select(isSelect: boolean) {
    this.selectClass = isSelect;  // 親から渡ってくるisSelectによってapp-selectにselectクラスを付与させたりさせなかったりできる
  }
}
```

ちなみに`@HostBinding`を指定した変数をreadonlyとすれば静的に付与させることも可能です。

```ts:select.component.ts
@Component({
  selector: 'app-select',
  templateUrl: './select.component.html',
  styleUrls: []
})
export class SelectComponent {
  @HostBinding('class.select') readonly selectClass: boolean = true; // 常にselectクラスを適用する
}
```

# まとめ
ということでコンポーネントに切り出したときにCSSスタイルの崩れが起こりうる現象とその解決方方法としてコンポーネント要素にクラスを付与する方法を紹介しました。
コンポーネント要素に属性を付与したい場合は

- 静的に付与したい→`@Component`のhostプロパティか`@HostBinding`で変数をreadonlyにして指定
- 動的に付与したい→`@HostBinding`で指定

で指定しましょう。

# おまけ：コンポーネント要素自体にCSSスタイルを適用する方法
クラスを付与するのではなく直接CSSを指定したい場合、`:host`セレクタを使用してCSSスタイルを適用することができます。

```html:select.component.html
<div class="select">
　<select>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
</div>
```

```ts:select.component.ts
@Component({
  selector: 'app-select',
  templateUrl: './select.component.html',
  styleUrls: ['./select.component.scss']
})
export class SelectComponent {
}
```

```scss:select.component.scss
:host { // :hostセレクタでコンポーネント要素自体に適用可能
  padding: 5px;
  width: 100%;
}
```

- [Angular 日本語ドキュメンテーション - コンポーネントスタイル](https://angular.jp/guide/component-styles)


# その他参考
- [How to share styles between Angular Components - DEV](https://dev.to/michaeljota/how-to-share-styles-between-angular-components-nhm)