はじめに
この記事の内容は正直邪道です(^^; (無理矢理やってます)
ここに手をくだそうとする時点で、デザイン方針の何かが間違っている気がしないでもない。
注意
この記事の解決策は以下の状況に当てはまる場合適用できません。
-
<mat-form-field>
タグの中に複数<input>
タグが存在し、それぞれに適用するスタイルを変えたい。 - コンポーネントごとにスタイルを綺麗に切り分けて実装している
やりたいこと
Form Field
実装したときの以下の赤枠余白を消したい。
ソース (HTML一部のみ)
detailform.component.html
<label class="bg-lightblue">ラジオボタン + テキストボックス</label>
<div>
<mat-radio-group aria-label="Select an option" [(ngModel)]="radioandtext.value">
<mat-radio-button class="radio-btn" *ngFor="let option of options" [value]="option">
{{option}}
</mat-radio-button>
<mat-form-field appearance="standard">
<input matInput [(ngModel)]="radioandtext.text" placeholder="">
</mat-form-field>
</mat-radio-group>
</div>
※bg-lightblue
とかradio-btn
は背景色とか余白とか調整しているだけで今回の話と無関係です。
元々ラジオボタンしかなかったところに「その他:テキストボックス」なんて追加すると、突然余白が現れて違和感あるなーってことがたまにある。多分。
解決策
SCSS
に以下を追加して、その定義したクラスremove-top
をmat-form-field
のクラスとして追加する!
そしてtsのComponentにも設定を追加!!
SCSS
detailform.component.scss
.remove-top {
>.mat-form-field-wrapper {
>.mat-form-field-flex {
>.mat-form-field-infix {
border-top: none;
}
}
}
}
HTML (.remove-top
のclass追加しただけ)
detailform.component.html
<label class="bg-lightblue">ラジオボタン + テキストボックス</label>
<div>
<mat-radio-group aria-label="Select an option" [(ngModel)]="radioandtext.value">
<mat-radio-button class="radio-btn" *ngFor="let option of options" [value]="option">
{{option}}
</mat-radio-button>
<mat-form-field appearance="standard" class="remove-top">
<input matInput [(ngModel)]="radioandtext.text" placeholder="">
</mat-form-field>
</mat-radio-group>
</div>
TypeScriptのコード (関係あるところ)
detailform.component.ts
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { Radioandtext } from '../model/radioandtext';
@Component({
selector: 'app-detailform',
templateUrl: './detailform.component.html',
styleUrls: ['./detailform.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class DetailformComponent implements OnInit {
radioandtext: Radioandtext = {
value: '',
text: ''
}
options: string[] = ["オプション1", "オプション2", "その他"];
constructor(
) { }
ngOnInit(): void {
}
}
```
後述しますが、大事なのは`ViewEncapsulation`
`@Component`の中の`encapsulation: ViewEncapsulation.None,`の部分です。
**(意味合いを知らない状態で適用するのは、危険なのでご注意ください!!)**
いろいろ省略しているので、全体のコードを見たい場合は以下Githubのコードを参照ください。
[Githubのコード](https://github.com/nekotokurasu11/angular-app-with-aws/tree/removetop/server/angular-app/src/app/detailform)
画面はこんな感じになります。
![スクリーンショット 2020-12-25 1.20.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277737/3ca0897c-0406-b201-3042-cd4101929876.png)
(黄色の部分が無くなった!!)
## 解決策を導き出すまでの道のり①~問題点の洗い出し~
まず、あのにっくき黄色部分が何者であるか、Chromeのデベロッパーツールで調べます。
![border-top.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277737/0c3633e2-478f-727e-0493-f1704581fe20.png)
(虱潰しに探した)結果は、どうやら`.mat-form-field-infix`の`border-top`の部分がこの余白の正体らしいと分かります。
方針としては、この`border-top`を打ち消すクラスを定義していくことになります。
ソースの再掲 (HTML一部のみ)
```detailform.component.html
<label class="bg-lightblue">ラジオボタン + テキストボックス</label>
<div>
<mat-radio-group aria-label="Select an option" [(ngModel)]="radioandtext.value">
<mat-radio-button class="radio-btn" *ngFor="let option of options" [value]="option">
{{option}}
</mat-radio-button>
<mat-form-field appearance="standard">
<input matInput [(ngModel)]="radioandtext.text" placeholder="">
</mat-form-field>
</mat-radio-group>
</div>
```
まず、`.mat-form-field-infix`がどこに付与されているのか探すことになるのですが、上述した通り、書いたHTMLの中には存在しない……
てなわけで、デベロッパーツール上のHTMLソースを確認します。
![mat-form-field-infix.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277737/9f8e55ce-d8e5-e491-3cfc-73068cdc9267.png)
どうやら`mat-form-field`タグの中の、`<div class="mat-form-field-wrapper ~">`の中の、`<div class="mat-form-field-flex ~">`の中にあるらしいということが分かる。
そう。クラス`.mat-form-field-infix`が存在する要素はAngular Materialの内部で定義されているものであり、実装者からしたら**編集できない場所にある**のである!!!
ここで「できない」と言って、諦めても大丈夫だと思う。
(だってAngular Materialの問題だから!!)
## 解決策を導き出すまでの道のり②~実装方法の検討~
無理矢理でも余白を変えたい。とそう思ったなら、
まず考えられる手段は「`.mat-form-field-infix`のスタイルの定義自体を変えてしまう」でしょうか。
CSS等に慣れている人なら当然の話ですが、もしも上記手段でスタイルを変更した場合、
クラス`.mat-form-field-infix`が付与された要素のスタイルは**全て**変わってしまう。
できるだけ限定的な影響範囲でスタイルを変えるのが、得策でしょう。
そこで、今回考えたのが、
編集できない`.mat-form-field-infix`が付与された要素ではなく、
編集できる、`mat-form-field`の要素にクラスを追加して、`mat-form-field`から`.mat-form-field-infix`が付与された要素のスタイルを定義する方法になります。
これが以下(再掲)
SCSS
```detailform.component.scss
.remove-top {
>.mat-form-field-wrapper {
>.mat-form-field-flex {
>.mat-form-field-infix {
border-top: none;
}
}
}
}
```
CSSなら
```css
.remove-top >.mat-form-field-wrapper >.mat-form-field-flex >.mat-form-field-infix {
border-top: none;
}
```
HTML (`remove-top`のclass追加しただけ)
```detailform.component.html
<label class="bg-lightblue">ラジオボタン + テキストボックス</label>
<div>
<mat-radio-group aria-label="Select an option" [(ngModel)]="radioandtext.value">
<mat-radio-button class="radio-btn" *ngFor="let option of options" [value]="option">
{{option}}
</mat-radio-button>
<mat-form-field appearance="standard" class="remove-top">
<input matInput [(ngModel)]="radioandtext.text" placeholder="">
</mat-form-field>
</mat-radio-group>
</div>
```
これで`mat-form-field`要素のクラスに`remove-top`を付与した中のスタイル**だけ**変更することができます。
影響範囲をできうる限り小さくできる寸法です。
逆にいうと、`remove-top`を付与した`mat-form-field`中のスタイルは全て変更されてしまうので[注意](#注意)で書いたように、「複数`<input>`タグが存在し、それぞれに適用するスタイルを変えたい」場合は、上記のSCSSは適用無理です。
ここからはAngular独自の話。
(また再掲)
TypeScriptのコード (関係あるところ)
``````detailform.component.ts
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { Radioandtext } from '../model/radioandtext';
@Component({
selector: 'app-detailform',
templateUrl: './detailform.component.html',
styleUrls: ['./detailform.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class DetailformComponent implements OnInit {
radioandtext: Radioandtext = {
value: '',
text: ''
}
options: string[] = ["オプション1", "オプション2", "その他"];
constructor(
) { }
ngOnInit(): void {
}
}
```
tsに加えた設定。
`@Component`の中の`encapsulation: ViewEncapsulation.None,`ですが、
これは、**カプセル化は一切行われず、すべてのスタイルをグローバルスコープに展開する**設定です。
「え、元々カプセル化されてたの?」という感じかもしれませんが、詳しくは以下参照でお願いします。
[公式](https://angular.jp/api/core/ViewEncapsulation)
[Angular 2: Component のスタイル実装と CSS のカプセル化](https://qiita.com/jimbo/items/b347c19d935e796c2482)
ざっくりいうと、何も設定しない場合は、カプセル化された状態になり、
例えば、別々のコンポーネントで同名のクラスを別々のスタイルとして定義しても、コンポーネントごとにスタイルが適用されます(競合することがない)
ここは私の予想・解釈ですが、`mat-form-field`の要素はAngular Materialで定義されている別のComponentのようなもので、
カプセル化された状態のままだと上記のSCSSは`mat-form-field`の中にスタイルをあてることはできないようです。
今回の方針だと`encapsulation: ViewEncapsulation.None,`は必須になります。
[注意](#注意)で書きましたが、コンポーネントごとにスタイルを綺麗に切り分けて実装している=カプセル化を大いに利用している場合は、この設定はできません。「Form Field上部余白を消す」のは無理、、ということになります。
## おわり
どうにかスタイルを微調節できないかと苦心した結果です(^^;
参考程度でお願いします〜