15

Since ngModel is updating instantly how to put a delay.

  <input type="text" value="{{item.task_name}}" name="task_name" [(ngModel)]="item.task_name" (ngModelChange)="update_fields([item.task_name])" >

I need to save the task_name with a delay of one seconds by calling update_fields() , To avoid instant calls to service.

Thanks

5 Answers5

32

Rxjs and Observables are the perfect candidate for this type of task! Here is an example of how it can be achieved:

Template:

<input type="text" [value]="item.task_name"(keyup)="term$.next($event.target.value)">

Component:

import ......

import {Subject} from 'rxjs/Subject';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap';

@Component{(
  ...
)}
export class YourComponent {

  term$ = new Subject<string>();

  constructor() {
    this.term$
      .debounceTime(1000)
      .distinctUntilChanged()
      .switchMap(term => /*do something*/);
  }
}

subject is a type of object that acts both as an observable and observer - meaning you can both subscribe to it and emit values from it (with next())!

debounceTime waits for the provided time in ms until it allows for new changes

distinctUntilChanges will not allow the same input to pass through two times in a row

switchMap takes the latest observable from the chain so you don't get multiple results at once

Fredrik Lundin
  • 8,006
  • 1
  • 29
  • 35
  • Sorry, I am a beginner, can you please update the import part also, Becoz "Cannot find name subject" error is showing,Even I added -- import {Observable} from "rxjs/Observable"; also –  Jun 27 '17 at 12:25
  • @JomyJoseph added imports to my answer :) – Fredrik Lundin Jun 27 '17 at 12:30
  • 5
    I just used `subscribe` instead of `switchMap` and it worked. – Gowtham Jul 17 '17 at 10:08
  • Not sure if it's how I'm using it, however I'm finding the switchMap function (Angular5) requires a return. So return Observable.empty() - or whatever for instance. – PeterS Jan 22 '18 at 09:13
14

Answer by Fredrik Lundin updated for Angular 6:

Template:

<input type="text" [value]="item.task_name" (keyup)="term$.next($event.target.value)">

Component:

import ......

import { Subject, EMPTY } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';

@Component{(
  ...
)}
export class YourComponent implements OnDestroy {

  term$ = new Subject<string>();

  private searchSubscription: Subscription;

  constructor() {
    this.searchSubscription = this.term$.pipe(
      debounceTime(1000),
      distinctUntilChanged(),
      switchMap(term => {
        /*do something*/
        return EMPTY;
      })
    ).subscribe();
  }

  ngOnDestroy() {
    //remember to unsubscribe on destroy

    if (this.searchSubscription) {
      this.searchSubscription.unsubscribe();
      this.searchSubscription = null;
    }
  }
}
vangras
  • 163
  • 2
  • 8
  • 1
    this is a great example that i am using, but the pipe doesn't have .'s in front of the functions, they need to be comma separated. – Ryan Jun 08 '19 at 22:58
  • This should be the accepted answer. Works like a charm. – Peter Jul 28 '19 at 05:51
  • @vangras can you maybe explain what's different or better about your solution compared to Fredrik's? – El Mac Oct 16 '20 at 07:10
3

Lots of solutions using setTimeout(), but this will cause the function to be called each time the model changes, a simple way to prevent this is to clear the timeout first

e.g.

timeOut;
timeOutDuration = 1000;

update_fields(data) {
  clearTimeout(this.timeOut);
  this.timeOut = setTimeout(() => {
     //do something
  }, this.timeOutDuration);
}

this will only call the function once after the last update is made and the timeOutDuration has elapsed

0
update_fields(){

  this.service.yourTask(){
    .subscribe(data => {
      setTimeout(()=>{ //your task }, 4000)
    }    
  }
}


someFunction() {
    setTimeout(() => /* code to execute */, 3000)
}
Rahul Singh
  • 19,030
  • 11
  • 64
  • 86
0

Here's a solution that works with a callback.

view template:

<input ... #element (ngModelChange)="delayAction(element, doSomething, [$event])">

component class:

    actionsByElements = new Map<HTMLElement, Subscription>();

    delayAction(element: HTMLElement, cb: Function, args: any[]) {
      // cancel countdown by element
      let action = this.actionsByElements.get(element);
      if(action) {
        action.unsubscribe();
      }

      // start new countdown by element
      action = timer(1000).subscribe(() => cb.apply(this, args));
      this.actionsByElements.set(element, action);
    }

    doSomething(){...}
Martin Cremer
  • 5,191
  • 2
  • 32
  • 38