はじめに
Angularではhtmlをコンポーネントに切り出すと、作成したコンポーネントも一つのDOMとなります。
そのためコンポーネントに切り出す前と比べてDOMの階層が1階層増えてしまいます。このときに起こる問題として、例えばDOMの親子関係を前提としたCSSを適用させていた場合に元のDOMの階層と変わってしまうがために親子関係が崩れ、適用したいCSSのスタイルが当たらなくなってしまうことがあります。
ここでは切り出したコンポーネント要素自体にclassや属性を付与することでそれを回避した方法を記します。
前提
Angular 9.1.0を前提としています。今後のバージョン次第で記述方法や動作が変わる可能性があります。
具体的にどんなときに起こる問題か
例えば以下のコードがあるとします。
<div class="form">
<input type="text"/>
<div class="select">
<select>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
<button>追加</button>
</div>
.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要素部分をコンポーネントに切り出すとします。
<div class="select">
<select>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
@Component({
selector: 'app-select',
templateUrl: './select.component.html',
styleUrls: []
})
export class SelectComponent {
}
<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の形式で与えることができます。
host: {
[key: string]: string;
}
例えば先程のapp-select
にクラスを付与する場合、以下のように書くことができます。
@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にはコンポーネント要素に付与したい属性を指定できます。
指定の仕方は<属性>.<属性名>
で指定します。
例えば先程のapp-select
にクラスを付与する場合、以下のように書くことができます。
@Component({
selector: 'app-select',
templateUrl: './select.component.html',
styleUrls: []
})
export class SelectComponent {
@HostBinding('class.select') selectClass: boolean = true; // trueの場合付与される
}
@HostBinding
は変数に付与できるため、クラスの付け外しを以下のように動的に行うことができます。
@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とすれば静的に付与させることも可能です。
@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スタイルを適用することができます。
<div class="select">
<select>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
@Component({
selector: 'app-select',
templateUrl: './select.component.html',
styleUrls: ['./select.component.scss']
})
export class SelectComponent {
}
:host { // :hostセレクタでコンポーネント要素自体に適用可能
padding: 5px;
width: 100%;
}