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


はじめに

この記事の内容は正直邪道です(^^; (無理矢理やってます)
ここに手をくだそうとする時点で、デザイン方針の何かが間違っている気がしないでもない。

注意

この記事の解決策は以下の状況に当てはまる場合適用できません。

  • <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-topmat-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のコード

画面はこんな感じになります。

(黄色の部分が無くなった!!)

解決策を導き出すまでの道のり①~問題点の洗い出し~

まず、あのにっくき黄色部分が何者であるか、Chromeのデベロッパーツールで調べます。

(虱潰しに探した)結果は、どうやら.mat-form-field-infixborder-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タグの中の、<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なら

.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,ですが、
これは、カプセル化は一切行われず、すべてのスタイルをグローバルスコープに展開する設定です。
「え、元々カプセル化されてたの?」という感じかもしれませんが、詳しくは以下参照でお願いします。
公式
Angular 2: Component のスタイル実装と CSS のカプセル化

ざっくりいうと、何も設定しない場合は、カプセル化された状態になり、
例えば、別々のコンポーネントで同名のクラスを別々のスタイルとして定義しても、コンポーネントごとにスタイルが適用されます(競合することがない)

ここは私の予想・解釈ですが、mat-form-fieldの要素はAngular Materialで定義されている別のComponentのようなもので、
カプセル化された状態のままだと上記のSCSSはmat-form-fieldの中にスタイルをあてることはできないようです。

今回の方針だとencapsulation: ViewEncapsulation.None,は必須になります。

注意で書きましたが、コンポーネントごとにスタイルを綺麗に切り分けて実装している=カプセル化を大いに利用している場合は、この設定はできません。「Form Field上部余白を消す」のは無理、、ということになります。

おわり

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