Angular and debounce

2019-01-02 17:18发布

In AngularJS I can debounce a model by using ng-model options.

ng-model-options="{ debounce: 1000 }"

How can I debounce a model in Angular? I tried to search for debounce in the docs but I couldn't find anything.

https://angular.io/search/#stq=debounce&stp=1

A solution would be to write my own debounce function, for example:

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);

And my html

<input type=text [value]="firstName" #first (keyup)="firstNameChanged($event, first)">

But I'm looking for a build in function, is there one in Angular?

15条回答
心情的温度
2楼-- · 2019-01-02 17:43

If you don't want to deal with @angular/forms, you can just use an RxJS Subject with change bindings.

view.component.html

<input [ngModel]='model' (ngModelChange)='changed($event)' />

view.component.ts

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);
    }
}

This does trigger change detection. For a way that doesn't trigger change detection, check out Mark's answer.


Update

.pipe(debounceTime(300), distinctUntilChanged()) is needed for rxjs 6.

Example:

   constructor() {
        this.modelChanged.pipe(
            debounceTime(300), 
            distinctUntilChanged())
            .subscribe(model => this.model = model);
    }
查看更多
爱死公子算了
3楼-- · 2019-01-02 17:44

Since the topic is old, most of the answers don't work on Angular 6.
So here is a short and simple solution for Angular 6 with RxJS.

Import necessary stuff first:

import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';

Initialize on ngOnInit:

export class MyComponent implements OnInit {
  notesText: string;
  notesModelChanged: Subject<string> = new Subject<string>();

  constructor() { }

  ngOnInit() {
    this.notesModelChanged
      .pipe(
        debounceTime(2000),
        distinctUntilChanged()
      )
      .subscribe(newText => {
        this.notesText = newText;
        console.log(newText);
      });
  }
}

Use this way:

<input [ngModel]='notesText' (ngModelChange)='notesModelChanged.next($event)' />

P.S.: For more complex and efficient solutions you might still want to check other answers.

查看更多
残风、尘缘若梦
4楼-- · 2019-01-02 17:50

Spent hours on this, hopefully I can save someone else some time. To me the following approach to using debounce on a control is more intuitive and easier to understand for me. It's built on the angular.io docs solution for autocomplete but with the ability for me to intercept the calls without having to depend on tying the data to the DOM.

Plunker

A use case scenario for this might be checking a username after it's typed to see if someone has already taken it, then warning the user.

Note: don't forget, (blur)="function(something.value) might make more sense for you depending on your needs.

查看更多
零度萤火
5楼-- · 2019-01-02 17:53

It could be implemented as Directive

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();
  }

}

use it like

<input [(ngModel)]="value" (onDebounce)="doSomethingWhenModelIsChanged($event)">

component sample

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);
    });
  }
} 
查看更多
忆尘夕之涩
6楼-- · 2019-01-02 17:53

For Reactive Forms and handling under Angular v2(latest) plus v4 look at:

https://github.com/angular/angular/issues/6895#issuecomment-290892514

Hopefully there will be native support for these kinds of things soon...

查看更多
梦寄多情
7楼-- · 2019-01-02 17:56

Simple solution would be to create a directive which you can apply to any control.

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);
                }


            });
    }
}

usage would be

<textarea [ngModel]="somevalue"   
          [debounce]="2000"
          (onDebounce)="somevalue = $event"                               
          rows="3">
</textarea>
查看更多
登录 后发表回答