Angular MaterialのForm Field上部余白を消す

## はじめに
この記事の内容は正直邪道です(^^;　（無理矢理やってます）
ここに手をくだそうとする時点で、デザイン方針の何かが間違っている気がしないでもない。  
## 注意
この記事の解決策は以下の状況に当てはまる場合適用できません。

- `<mat-form-field>`タグの中に複数`<input>`タグが存在し、それぞれに適用するスタイルを変えたい。
- コンポーネントごとにスタイルを綺麗に切り分けて実装している

## やりたいこと
`Form Field`実装したときの以下の赤枠余白を消したい。
![スクリーンショット 2020-12-22 1.55.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277737/088ef707-a6fa-4ea7-e2b8-cc836fc33dd9.png)

ソース (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上部余白を消す」のは無理、、ということになります。

## おわり
どうにかスタイルを微調節できないかと苦心した結果です(^^;
参考程度でお願いします〜