Angularのフォームにdebounce機能を付ける方法について、ハマった為備忘録兼誰かの助けになればと思い記載していく。
※新米なので優しく見てください。
間違いなどあれば優しくコメントで指摘してください。優しく
実行環境
Angular/cli 11.2.19
nodeJS 12.16.3
npm 6.14.4
そもそもdebounceとは?
debounce(デバウンス) とは処理に遅延を走らせること。
具体的には、フォーム等に文字が入力された際にはinputイベントが走り、即時に処理が行われる。
debounceを実装していないと入力した文字を表示する場合は、入力して即座に表示される。
debounce無し:入力すると即座に反映される。
debounce有り:入力して指定の時間の後に表示される。(debounce無しと同じ状態になる)
debounceとsetTimeOutとの違い
setTimeOutをあまり理解していないため間違っているかも...スミマセン...
debounce
debounceの処理の動きは最後のイベントが終了してから指定の時間後に処理が走るという動き。
【テキストボックスの場合(指定遅延時間0.5秒)】
入力中に0.4秒以内に次の文字を入力すれば処理は走らない。
最後に入力してから0.4秒後に処理が走る。
setTimeOunt(間違っているカモ)
イベントが起こったら途中とか関係なく指定の時間後に処理が走るという動き。
【テキストボックスの場合(指定遅延時間0.5秒)】
文字を入力した後0.5秒後に処理が走る(途中とか関係なく)
イベントに対してsetTimeOutを指定した場合は、入力した文字それぞれに対して0.5秒後に処理が走ると思う。
フォームで使うならdebounceの方がよさそう!
Angularでdebounceの実装方法
結論
(formControl).valueChanges.pipe(
debounceTime(500),
).subscribe((value: any) => {
}
を使うと実装できる。
特に注目すべきは debounceTime(500) である。
具体的な説明の前に全体コードを記載する。
全体コード
app.component.ts
import { Component } from '@angular/core';
import { FormControl, FormGroup } from "@angular/forms";
import { debounceTime } from 'rxjs/operators'; // 1.debounceTimeを使用する場合はimportが必要
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
message : any = '';
inputTextBox: FormControl = new FormControl(); // 2.ReactiveFormを定義
formGroup = new FormGroup({
inputTextBox: this.inputTextBox
});
inputText(): void {
this.inputTextBox.valueChanges.pipe( // 3.処理を遅らせるFormControlを指定する
debounceTime(500), // 4.処理を遅らせる時間を指定
).subscribe((value: any) => { // 5.遅らせた処理のvalueに対する処理を設定
this.message = value; // 6.value = テキストボックスの入力値をmessageに代入。
});
}
}
app.component.html
<form [formGroup]="formGroup">
<input
type="text"
(input)="inputText()"
[formControl]="inputTextBox"
>
{{message}} <!-- テキストボックスの横に表示させる文字列(変数) -->
</form>
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { InputTextComponent } from './src/shared/components/atoms/input-text/input-text.component';
import { ReactiveFormsModule } from '@angular/forms'; // ReactiveFormを使用するためにimportが必要
@NgModule({
declarations: [
AppComponent,
InputTextComponent
],
imports: [
BrowserModule,
AppRoutingModule,
ReactiveFormsModule, // これも必要。
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
具体的に説明
app.component.ts
import { Component } from '@angular/core';
import { FormControl, FormGroup } from "@angular/forms";
import { debounceTime } from 'rxjs/operators'; // 1.debounceTimeを使用する場合はimportが必要
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
message : any = '';
inputTextBox: FormControl = new FormControl(); // 2.ReactiveFormを定義
formGroup = new FormGroup({
inputTextBox: this.inputTextBox
});
inputText(): void {
this.inputTextBox.valueChanges.pipe( // 3.処理を遅らせるFormControlを指定する
debounceTime(500), // 4.処理を遅らせる時間を指定
).subscribe((value: any) => { // 5.遅らせた処理のvalueに対する処理を設定
this.message = value; // 6.value = テキストボックスの入力値をmessageに代入。
});
}
}
1 debounceTimeをimport
import { debounceTime } from 'rxjs/operators'; // 1.debounceTimeを使用する場合はimportが必要
コピペでOK。
これをimportしないと使えない。
2 ReactiveFormを定義(実装)
inputTextBox: FormControl = new FormControl(); // 2.ReactiveFormを定義
formGroup = new FormGroup({
inputTextBox: this.inputTextBox
});
入力を行うテキストボックスにReactiveFormを実装する。
3.4.5.6 debounceの実装
inputText(): void {
this.inputTextBox.valueChanges.pipe( // 3.処理を遅らせるFormControlを指定する
debounceTime(500), // 4.処理を遅らせる時間を指定
).subscribe((value: any) => { // 5.遅らせた処理のvalueに対する処理を設定
this.message = value; // 6.value = テキストボックスの入力値をmessageに代入。
});
}
3 FormControlの指定
デバウンスを実装するFormControlに対して.valueChanges.pipe()を実装する。
valueChanges.pipe()でvalue(入力された値)に対して加工ができるみたいなのだった気がする...
4 遅延させる時間を指定
3のpipeで値を加工できるようにした後に処理を遅らせるdebounceTimeを指定する。
引数にミリ秒を指定できる。500なら0.5秒
5 遅延させたvalueに対しての処理を指定
.subscribe((value: any) => {}
と書くことで、valueに実際に入力されて、遅延処理が施された値が代入される。
{}の中でvalueを使って任意の処理が可能。(普通のアロー演算子なのでvalueでなくても任意の変数でOK)
6 実際のvalueの処理
今回の場合では入力値を変数に代入して、app.component.htmlでその変数を表示させる。
応用
subscribeの中でvalueを使う必要はない!
↓
処理を遅らせる為だけにdebounceを使うのもあり
例えば入力処理が終わった後0.5秒後にバリデーションエラーメッセージを表示させるという処理だったら、下記コードでエラーメッセージを遅延させて表示させることができる。
app.component.ts
inputText(): void {
this.inputTextBox.valueChanges.pipe(
debounceTime(500),
).subscribe((value: any) => {
this.validateStatus = this.inputTextBox.invalid;
});
}
app.component.html
<p *ngIf="validateStatus">バリデーションエラー<p>
このようにsubscribeまでに遅延があり、そのあとsubscribe内の処理を行う。
value関係なしに使うことも可能。
※このような本来の使い方と違う使い方が良いかはどうかはわからない...
まとめ
フォームで遅延処理を実装するときにdebounceを使うことはよさそう!
他にもFormControlを継承した新たなオブジェクトを作成して、そこでdebounceを作ってしまう方法もあるが、めんどくさそう。
debounceの重要性を調べた際に、バリデーションエラーが入力中に出てくると、ユーザ的に不満を感じるから必要!みたいな記事を見たが、フォーカスが外れた場合とかではダメなのかな?と思った。
なんせAngularでのdebounce実装は面倒臭すぎる(フォーカスが外れた時にバリデーション!なら簡単に実装できる)。
Vue.jsとかだったらなんかライブラリがあって簡単にdebounceできるらしい...
このdebounceの使い方にたどり着くまで非常に時間がかかった。
Angularはネット上に情報が少なすぎる!
あったとしてもAngular2の情報など・・・古すぎる!
もしかしてAngularってあんまり使われていないのでしょうか?