AngularJSでは、ng-modelオプションを使用してモデルをデバウンスできます。
ng-model-options="{ debounce: 1000 }"
Angularでモデルをデバウンスする方法ドキュメントでデバウンスを検索しようとしましたが、何も見つかりませんでした。
https://angular.io/search/#stq=debounce&stp=1
解決策は、例えば私自身のデバウンス関数を書くことでしょう。
import {Component, Template, bootstrap} from 'angular2/angular2';
// Annotation section
@Component({
selector: 'my-app'
})
@Template({
url: 'app.html'
})
// Component controller
class MyAppComponent {
constructor() {
this.firstName = 'Name';
}
changed($event, el){
console.log("changes", this.name, el.value);
this.name = el.value;
}
firstNameChanged($event, first){
if (this.timeoutId) window.clearTimeout(this.timeoutID);
this.timeoutID = window.setTimeout(() => {
this.firstName = first.value;
}, 250)
}
}
bootstrap(MyAppComponent);
そして私のHTML
<input type=text [value]="firstName" #first (keyup)="firstNameChanged($event, first)">
しかし、私は組み込み関数を探しています、Angularにはありますか?
@angular/forms
を処理したくない場合は、変更バインディングでRxJS Subject
を使用するだけです。
<input [ngModel]='model' (ngModelChange)='changed($event)' />
import { Subject } from 'rxjs/Subject';
import { Component } from '@angular/core';
import 'rxjs/add/operator/debounceTime';
export class ViewComponent {
model: string;
modelChanged: Subject<string> = new Subject<string>();
constructor() {
this.modelChanged
.debounceTime(300) // wait 300ms after the last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(model => this.model = model);
}
changed(text: string) {
this.modelChanged.next(text);
}
}
これは変化検出を引き起こします。 変更検出を引き起こさないようにするためには、Markの答えを調べてください。
.pipe(debounceTime(300), distinctUntilChanged())
はrxjs 6に必要です。
例:
constructor() {
this.modelChanged.pipe(
debounceTime(300),
distinctUntilChanged())
.subscribe(model => this.model = model);
}
それは指令として実行することができます
import { Directive, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
import { NgControl } from '@angular/forms';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import { Subscription } from 'rxjs';
@Directive({
selector: '[ngModel][onDebounce]',
})
export class DebounceDirective implements OnInit, OnDestroy {
@Output()
public onDebounce = new EventEmitter<any>();
@Input('debounce')
public debounceTime: number = 300;
private isFirstChange: boolean = true;
private subscription: Subscription;
constructor(public model: NgControl) {
}
ngOnInit() {
this.subscription =
this.model.valueChanges
.debounceTime(this.debounceTime)
.distinctUntilChanged()
.subscribe(modelValue => {
if (this.isFirstChange) {
this.isFirstChange = false;
} else {
this.onDebounce.emit(modelValue);
}
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
好きに使う
<input [(ngModel)]="value" (onDebounce)="doSomethingWhenModelIsChanged($event)">
成分サンプル
import { Component } from "@angular/core";
@Component({
selector: 'app-sample',
template: `
<input[(ngModel)]="value" (onDebounce)="doSomethingWhenModelIsChanged($event)">
<input[(ngModel)]="value" (onDebounce)="asyncDoSomethingWhenModelIsChanged($event)">
`
})
export class SampleComponent {
value: string;
doSomethingWhenModelIsChanged(value: string): void {
console.log({ value });
}
async asyncDoSomethingWhenModelIsChanged(value: string): Promise<void> {
return new Promise<void>(resolve => {
setTimeout(() => {
console.log('async', { value });
resolve();
}, 1000);
});
}
}
Angular1のように直接アクセスすることはできませんが、NgFormControlとRxJSの観測量で簡単に遊ぶことができます。
<input type="text" [ngFormControl]="term"/>
this.items = this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term => this.wikipediaService.search(term));
このブログ記事はそれを明確に説明します: http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html
これはオートコンプリート用ですが、すべてのシナリオで機能します。
あなたはRxJS(v.6) Observable を作ることができます。
<input type="text" (input)="onSearchChange($event.target.value)" />
import { Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
export class ViewComponent {
searchChangeObserver;
onSearchChange(searchValue: string) {
if (!this.searchChangeObserver) {
Observable.create(observer => {
this.searchChangeObserver = observer;
}).pipe(debounceTime(300)) // wait 300ms after the last event before emitting last event
.pipe(distinctUntilChanged()) // only emit if value is different from previous value
.subscribe(console.log);
}
this.searchChangeObserver.next(searchValue);
}
}
Lodashを使っている人なら誰でも、 デバウンス することは非常に簡単です。
changed = _.debounce(function() {
console.log("name changed!");
}, 400);
それからちょうどあなたのテンプレートにこのようなものを投げる:
<input [ngModel]="firstName" (ngModelChange)="changed()" />
トピックが古いので、ほとんどの答えは動作しませんonAngular 6/7です。
したがって、RxJSを使ったAngular 6+の短くて簡単な解決策は次のとおりです。
まず必要なものをインポートしてください。
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
ngOnInit
で初期化します。
export class MyComponent implements OnInit, OnDestroy {
notesText: string;
private notesModelChanged: Subject<string> = new Subject<string>();
private notesModelChangeSubscription: Subscription
constructor() { }
ngOnInit() {
this.notesModelChangeSubscription = this.notesModelChanged
.pipe(
debounceTime(2000),
distinctUntilChanged()
)
.subscribe(newText => {
this.notesText = newText;
console.log(newText);
});
}
ngOnDestroy() {
this.notesModelChangeSubscription.unsubscribe();
}
}
このようにしてください。
<input [ngModel]='notesText' (ngModelChange)='notesModelChanged.next($event)' />
P.S .:より複雑で効率的な解決策のために、あなたはまだ他の答えをチェックしたいかもしれません。
私はデバウンスデコレータを書くことでこれを解決しました。説明した問題は、@debounceAccessorをプロパティのsetアクセサに適用することで解決できます。
私はまた、メソッド用の追加のデバウンスデコレータも提供しました。
これにより、プロパティやメソッドをデバウンスするのが非常に簡単になります。パラメータは、デバウンスが続くミリ秒数です。以下の例では100ミリ秒です。
@debounceAccessor(100)
set myProperty(value) {
this._myProperty = value;
}
@debounceMethod(100)
myMethod (a, b, c) {
let d = a + b + c;
return d;
}
これがデコレータのコードです。
function debounceMethod(ms: number, applyAfterDebounceDelay = false) {
let timeoutId;
return function (target: Object, propName: string, descriptor: TypedPropertyDescriptor<any>) {
let originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
if (timeoutId) return;
timeoutId = window.setTimeout(() => {
if (applyAfterDebounceDelay) {
originalMethod.apply(this, args);
}
timeoutId = null;
}, ms);
if (!applyAfterDebounceDelay) {
return originalMethod.apply(this, args);
}
}
}
}
function debounceAccessor (ms: number) {
let timeoutId;
return function (target: Object, propName: string, descriptor: TypedPropertyDescriptor<any>) {
let originalSetter = descriptor.set;
descriptor.set = function (...args: any[]) {
if (timeoutId) return;
timeoutId = window.setTimeout(() => {
timeoutId = null;
}, ms);
return originalSetter.apply(this, args);
}
}
}
私はあなたがデバウンス遅延の後にメソッドを起動させようというメソッドデコレータのための追加のパラメータを追加しました。私はそれをしたので、例えばマウスオーバーイベントやサイズ変更イベントと組み合わせたときにそれを使うことができました、そこで私はキャプチャがイベントストリームの終わりに起こることを望みました。ただしこの場合、メソッドは値を返しません。
NgModelのデフォルトのviewToModelUpdate関数を空のもので上書きする[debounce]ディレクティブを作成できます。
指令コード
@Directive({ selector: '[debounce]' })
export class MyDebounce implements OnInit {
@Input() delay: number = 300;
constructor(private elementRef: ElementRef, private model: NgModel) {
}
ngOnInit(): void {
const eventStream = Observable.fromEvent(this.elementRef.nativeElement, 'keyup')
.map(() => {
return this.model.value;
})
.debounceTime(this.delay);
this.model.viewToModelUpdate = () => {};
eventStream.subscribe(input => {
this.model.viewModel = input;
this.model.update.emit(input);
});
}
}
どうやって使うのですか
<div class="ui input">
<input debounce [delay]=500 [(ngModel)]="myData" type="text">
</div>
HTMLファイル
<input [ngModel]="filterValue"
(ngModelChange)="filterValue = $event ; search($event)"
placeholder="Search..."/>
TSファイル
timer = null;
time = 250;
search(searchStr : string) : void {
clearTimeout(this.timer);
this.timer = setTimeout(()=>{
console.log(searchStr);
}, time)
}
簡単な解決策は、任意のコントロールに適用できるディレクティブを作成することです。
import { Directive, ElementRef, Input, Renderer, HostListener, Output, EventEmitter } from '@angular/core';
import { NgControl } from '@angular/forms';
@Directive({
selector: '[ngModel][debounce]',
})
export class Debounce
{
@Output() public onDebounce = new EventEmitter<any>();
@Input('debounce') public debounceTime: number = 500;
private modelValue = null;
constructor(public model: NgControl, el: ElementRef, renderer: Renderer){
}
ngOnInit(){
this.modelValue = this.model.value;
if (!this.modelValue){
var firstChangeSubs = this.model.valueChanges.subscribe(v =>{
this.modelValue = v;
firstChangeSubs.unsubscribe()
});
}
this.model.valueChanges
.debounceTime(this.debounceTime)
.distinctUntilChanged()
.subscribe(mv => {
if (this.modelValue != mv){
this.modelValue = mv;
this.onDebounce.emit(mv);
}
});
}
}
使い方は
<textarea [ngModel]="somevalue"
[debounce]="2000"
(onDebounce)="somevalue = $event"
rows="3">
</textarea>
これに時間を費やした、うまくいけば私は他の誰かをしばらく時間を節約することができます。私には、コントロールでdebounce
を使用するための次のアプローチがより直感的で理解しやすいものになっています。これは、オートコンプリート用のangular.io docsソリューションに基づいて構築されていますが、データをDOMに結び付けることに頼ることなく、呼び出しを傍受することができます。
このユースケースシナリオでは、入力後にユーザー名を調べて、他のユーザーが既に持っているかどうかを確認してから、ユーザーに警告します。
注意:忘れないでください、あなたのニーズによっては(blur)="function(something.value)
があなたにとってもっと理にかなっているかもしれません。
RxJS v6でのAngular 7のDebounceTime
ソース リンク
デモ リンク
HTMLテンプレート内
<input type="text" #movieSearchInput class="form-control"
placeholder="Type any movie name" [(ngModel)]="searchTermModel" />
コンポーネント内
....
....
export class AppComponent implements OnInit {
@ViewChild('movieSearchInput') movieSearchInput: ElementRef;
apiResponse:any;
isSearching:boolean;
constructor(
private httpClient: HttpClient
) {
this.isSearching = false;
this.apiResponse = [];
}
ngOnInit() {
fromEvent(this.movieSearchInput.nativeElement, 'keyup').pipe(
// get value
map((event: any) => {
return event.target.value;
})
// if character length greater then 2
,filter(res => res.length > 2)
// Time in milliseconds between key events
,debounceTime(1000)
// If previous query is diffent from current
,distinctUntilChanged()
// subscription for response
).subscribe((text: string) => {
this.isSearching = true;
this.searchGetCall(text).subscribe((res)=>{
console.log('res',res);
this.isSearching = false;
this.apiResponse = res;
},(err)=>{
this.isSearching = false;
console.log('error',err);
});
});
}
searchGetCall(term: string) {
if (term === '') {
return of([]);
}
return this.httpClient.get('http://www.omdbapi.com/?s=' + term + '&apikey=' + APIKEY,{params: PARAMS.set('search', term)});
}
}
初期化サブスクライバーを直接イベント機能に使用した解決策:
import {Subject} from 'rxjs';
import {debounceTime, distinctUntilChanged} from 'rxjs/operators';
class MyAppComponent {
searchTermChanged: Subject<string> = new Subject<string>();
constructor() {
}
onFind(event: any) {
if (this.searchTermChanged.observers.length === 0) {
this.searchTermChanged.pipe(debounceTime(1000), distinctUntilChanged())
.subscribe(term => {
// your code here
console.log(term);
});
}
this.searchTermChanged.next(event);
}
}
そしてhtml:
<input type="text" (input)="onFind($event.target.value)">
これは私が今まで見つけた中で最高の解決策です。 ngModel
on blur
とdebounce
を更新します
import { Directive, Input, Output, EventEmitter,ElementRef } from '@angular/core';
import { NgControl, NgModel } from '@angular/forms';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/map';
@Directive({
selector: '[ngModel][debounce]',
})
export class DebounceDirective {
@Output()
public onDebounce = new EventEmitter<any>();
@Input('debounce')
public debounceTime: number = 500;
private isFirstChange: boolean = true;
constructor(private elementRef: ElementRef, private model: NgModel) {
}
ngOnInit() {
const eventStream = Observable.fromEvent(this.elementRef.nativeElement, 'keyup')
.map(() => {
return this.model.value;
})
.debounceTime(this.debounceTime);
this.model.viewToModelUpdate = () => {};
eventStream.subscribe(input => {
this.model.viewModel = input;
this.model.update.emit(input);
});
}
}
https://stackoverflow.com/a/47823960/3955513 から借りたもの
HTMLでは:
<input [(ngModel)]="hero.name"
[debounce]="3000"
(blur)="hero.name = $event.target.value"
(ngModelChange)="onChange()"
placeholder="name">
blur
では、モデルはプレーンJavaScriptを使用して明示的に更新されます。