2020年10月23日金曜日

Angular・リアクティブフォームにおけるdisabled属性の扱いについて。


はじめまして、オフィス狛 技術部のpinoと申します。

今回は、Angularのリアクティブフォームにおけるdisabled属性の扱いについて書いてみたいと思います。

先日、Angularの実装中に以下のWarningに出会いました。
It looks like you're using the disabled attribute with a reactive form directive. If you set disabled to true
when you set up this control in your component class, the disabled attribute will actually be set in the DOM for
you. We recommend using this approach to avoid 'changed after checked' errors.

Example:
form = new FormGroup({
     first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
     last: new FormControl('Drew', Validators.required)
});

(かなり砕いた感じに)意訳すると、リアクティブフォームを使っているのに、disabled属性をHTMLに直に設定していませんか?例の書き方ならdisabled属性をDOMで設定することができます!といった具合でしょうか。

Warningなので最悪このままにしておいてもいいかもしれませんが、できることなら解消しておきたいですよね。

上記のWarningは、文言に忠実に従うことで回避できます。
以下、応用も含めて例を紹介します!

Formの定義

修正前は、以下のFormを定義していることにします。
(わかりやすく、Warningの文中にあった例を流用したいと思います。)
HTML
... // 中略

  <form [formGroup]="form">
    <input type="text" disabled formControlName="first">
    <input type="text" formControlName="last">
  </form>

...
TS
... // 中略

  form = new FormGroup({
    first: new FormControl('Nancy', Validators.required),
    last: new FormControl('Drew', Validators.required)
  });

...
FormBuilderを使用する場合
... // 中略

  form = this.fb.group({
    first: ['Nancy', Validators.required],
    last: ['Drew', Validators.required]
  });

  constructor(private fb: FormBuilder) { }

...
* FormBuilderを使用する場合は、DIが必要です。 以降は、記述を省略します。

Warningの出る書き方

HTMLで以下のようにdisabled属性を設定していると、上記のWarningが出ます。
例)
  // ①HTMLの書き方そのまま
  <input type="text" disabled>

  // ②バインディング構文使用、設定値は固定
  <input type="text" [disabled]="true">

  // ③バインディング構文使用、設定値は可変
  <input type="text" [disabled]="form.invalid">

③もダメなんですね...。


解消方法

1. 基本に従う
Warningの文言にある通りに書き換えます。
HTML
... // 中略

  <form [formGroup]="form">
    <input type="text" formControlName="first">
    <input type="text" formControlName="last">
  </form>

...
TS
... // 中略

  form = new FormGroup({
    first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
    last: new FormControl('Drew', Validators.required)
  });

...

HTMLからdisabledの記述を無くし、
コンポーネント側に {value: 'Nancy', disabled: true} という記述を増やしました。
ちなみにdisabled: trueで非アクティブ、disabled: falseでアクティブとなります。

FormBuilderを使用する場合は、以下となります。
... // 中略

  form = this.fb.group({
    first: [{value: 'Nancy', disabled: true}, Validators.required],
    last: ['Drew', Validators.required]
  });

...
こちらも、{value: 'Nancy', disabled: true} を追記すればOKです。

2. 動的に設定する
動的に切り替えたい時の方法です。
HTML
... // 中略

  <input type="text" formControlName="first">
  <input type="text" formControlName="last">
  <input type="checkbox" (click)="switchDisabled()">チェックするとfirstが非アクティブになります

...
TS
... // 中略

  isDisabled = true;

  form = this.fb.group({
    first: [{value: 'Nancy', disabled: this.isDisabled}, Validators.required],
    last: ['Drew', Validators.required]
  });

...

  switchDisabled() {
    if (this.isDisabled) {
      this.form.controls.address.enable();
    } else {
      this.form.controls.address.disable();
    }
    this.isDisabled = !this.isDisabled;
  }

...

切り替え用のチェックボックスを置いてみました。
ポイントは、コンポーネント側のenable()disable()です。
これにより、アクティブ・非アクティブを切り替えることができます。

今回はチェックボックスを使用しましたが切り替えるためのトリガーはなんでもOKですし、
切り替えが必要ない場合は、ngOnInit()に処理を置いて初期状態の判断に使用するのも良さそうです!

補足

ここまで説明してきましたが、実はdisabledをこのように指定しなくてもWarningが出ない要素もあります。
今のところそのように把握しているのは、button要素とoption要素です。

さらに、前述したWarningの出る書き方の例でいうと

button要素 → ①, ②, ③ どの書き方をしても怒られない
option要素 → ①は怒られる。②と③なら怒られない

という結果になりました。

この辺りは、余裕ができたらもう少し詳しく調べてみたいですね。


今回はここまで。
簡単な内容だったと思いますが、参考になれば嬉しいです。
,

0 件のコメント:

コメントを投稿