Угловой и отказов


160

В AngularJS я могу опровергнуть модель, используя параметры ng-model.

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

Как я могу разоблачить модель в Angular? Я пытался найти debounce в документах, но ничего не смог найти.

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

Решением было бы написать мою собственную функцию debounce, например:

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?


3
Это может быть актуально для github.com/angular/angular/issues/1773 , но еще не видимо.
Эрик Мартинес

Проверьте этот пост на Angular 7 с RxJS v6 freakyjolly.com/…
Code Spy

Ответы:


202

Обновлено для RC.5

С Angular 2 мы можем отсеивать, используя оператор RxJS debounceTime()в valueChangesнаблюдаемом элементе управления формы :

import {Component}   from '@angular/core';
import {FormControl} from '@angular/forms';
import {Observable}  from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/throttleTime';
import 'rxjs/add/observable/fromEvent';

@Component({
  selector: 'my-app',
  template: `<input type=text [value]="firstName" [formControl]="firstNameControl">
    <br>{{firstName}}`
})
export class AppComponent {
  firstName        = 'Name';
  firstNameControl = new FormControl();
  formCtrlSub: Subscription;
  resizeSub:   Subscription;
  ngOnInit() {
    // debounce keystroke events
    this.formCtrlSub = this.firstNameControl.valueChanges
      .debounceTime(1000)
      .subscribe(newValue => this.firstName = newValue);
    // throttle resize events
    this.resizeSub = Observable.fromEvent(window, 'resize')
      .throttleTime(200)
      .subscribe(e => {
        console.log('resize event', e);
        this.firstName += '*';  // change something to show it worked
      });
  }
  ngDoCheck() { console.log('change detection'); }
  ngOnDestroy() {
    this.formCtrlSub.unsubscribe();
    this.resizeSub  .unsubscribe();
  }
} 

Plunker

Приведенный выше код также включает в себя пример того, как регулировать события изменения размера окна, как это спросил @albanx в комментарии ниже.


Хотя приведенный выше код, вероятно, является угловым способом сделать это, он неэффективен. Каждое нажатие клавиши и каждое событие изменения размера, даже если они отменяются и регулируются, приводит к выполнению обнаружения изменений. Другими словами, устранение неполадок и регулирование не влияют на частоту выполнения обнаружения изменений . (Я нашел комментарий GitHub от Тобиаса Боша, который подтверждает это.) Это можно увидеть, когда вы запускаете плункер, и видите, сколько раз ngDoCheck()вызывается, когда вы вводите в поле ввода или изменяете размер окна. (Используйте синюю кнопку «x» для запуска плунжера в отдельном окне, чтобы увидеть события изменения размера.)

Более эффективный метод заключается в создании RxJS Observables самостоятельно из событий за пределами «зоны» Angular. Таким образом, обнаружение изменений не вызывается каждый раз, когда происходит событие. Затем в ваших методах обратного вызова подписки инициируйте обнаружение изменений вручную, т.е. вы управляете, когда вызывается обнаружение изменений:

import {Component, NgZone, ChangeDetectorRef, ApplicationRef, 
        ViewChild, ElementRef} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/throttleTime';
import 'rxjs/add/observable/fromEvent';

@Component({
  selector: 'my-app',
  template: `<input #input type=text [value]="firstName">
    <br>{{firstName}}`
})
export class AppComponent {
  firstName = 'Name';
  keyupSub:  Subscription;
  resizeSub: Subscription;
  @ViewChild('input') inputElRef: ElementRef;
  constructor(private ngzone: NgZone, private cdref: ChangeDetectorRef,
    private appref: ApplicationRef) {}
  ngAfterViewInit() {
    this.ngzone.runOutsideAngular( () => {
      this.keyupSub = Observable.fromEvent(this.inputElRef.nativeElement, 'keyup')
        .debounceTime(1000)
        .subscribe(keyboardEvent => {
          this.firstName = keyboardEvent.target.value;
          this.cdref.detectChanges();
        });
      this.resizeSub = Observable.fromEvent(window, 'resize')
        .throttleTime(200)
        .subscribe(e => {
          console.log('resize event', e);
          this.firstName += '*';  // change something to show it worked
          this.cdref.detectChanges();
        });
    });
  }
  ngDoCheck() { console.log('cd'); }
  ngOnDestroy() {
    this.keyupSub .unsubscribe();
    this.resizeSub.unsubscribe();
  }
} 

Plunker

Я использую ngAfterViewInit()вместо того, ngOnInit()чтобы убедиться, что inputElRefэто определено.

detectChanges()запустит обнаружение изменений на этом компоненте и его дочерних элементах. Если вы предпочитаете запускать обнаружение изменений из корневого компонента (т. Е. Выполнять полную проверку обнаружения изменений), используйте ApplicationRef.tick()вместо этого. (Я помещаю вызов ApplicationRef.tick()в комментарии в плунжере.) Обратите внимание, что вызов tick()вызовет ngDoCheck()вызов.


2
@Mark Rajcok Я думаю, что вместо [value] вы должны использовать [ngModel], потому что [value] не обновляет входное значение.
Милад

1
Есть ли какой-либо общий метод debounce (например, применить к событию изменения размера окна)?
albanx

1
@MarkRajcok Я полагаю, что проблема с компакт-диском, которую вы описали в своем ответе, решена github.com/angular/zone.js/pull/843
Jefftopia

2
Когда нам нужно отписаться, чтобы предотвратить утечки памяти?
клевета

1
@slanden Да, согласно netbasal.com/when-to-unsubscribe-in-angular-d61c6b21bad3 , мы должны отказаться от .fromEvent()подписки
Джон Онстотт

153

Если вы не хотите иметь дело @angular/forms, вы можете просто использовать RxJS Subjectс привязками изменений.

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

Это вызывает обнаружение изменений. Для способа, который не вызывает обнаружение изменений, проверьте ответ Марка.


Обновить

.pipe(debounceTime(300), distinctUntilChanged()) нужен для rxjs 6.

Пример:

   constructor() {
        this.modelChanged.pipe(
            debounceTime(300), 
            distinctUntilChanged())
            .subscribe(model => this.model = model);
    }

5
Я предпочитаю это решение! Работал с угловой 2.0.0, rxjs 5.0.0-бета 12
alsco77

2
Работал отлично, просто и понятно, без формы. Я на Angular 4.1.3, rxjs 5.1.1
пятый

Я думаю, что это превосходное решение, так как оно имеет возможность работать с формами, если необходимо, но удаляет эту зависимость, делая реализацию намного проще. Спасибо.
Макс

2
.pipe(debounceTime(300), distinctUntilChanged())нужен для rxjs 6
Icycool

Решение спасло меня. Я использовал keyUpсобытие on input.nativeElementв mat-table, которое перестало работать, когда количество столбцов было изменено
igorepst

35

Это может быть реализовано как директива

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

1
с большим количеством импорта, который работал для меня: import "rxjs / add / operator / debounceTime"; import "rxjs / add / operator / differentUntilChanged";
суббота

2
Это, безусловно, делает его самым простым для реализации приложений
joshcomley

1
isFirstChange используется, чтобы не излучать при инициализации
Олег Полезский

2
Работает в Angular 8 и rxjs 6.5.2 со следующими изменениями. Если вы хотите использовать синтаксис канала, измените следующее: import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged';на import { debounceTime, distinctUntilChanged } from 'rxjs/operators';и this.model.valueChanges .debounceTime(this.debounceTime) .distinctUntilChanged()наthis.model.valueChanges .pipe( debounceTime(this.debounceTime), distinctUntilChanged() )
kumaheiyama

1
Работает в Angular 9 и rxjs 6.5.4 с изменениями @kumaheiyama, указанными в его комментарии. Только не забудьте экспортировать директиву в модуль, где вы ее создаете. И не забудьте включить модуль, в котором вы создаете эту директиву, в модуль, где вы ее используете.
Филип Савич

29

Поскольку тема старая, большинство ответов не работают на Angular 6/7/8/9 и / или не используют другие библиотеки.
Итак, вот краткое и простое решение для Angular 6+ с RxJS.

Сначала импортируйте необходимые вещи:

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 {
  public 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)' />

PS: для более сложных и эффективных решений вы все еще можете проверить другие ответы.


1
Почему бы вам не отписаться об этом на уничтожить?
Вирендра Сингх Ратор

Обновлено. Спасибо, что заметили!
Just Shadow

1
@ JustShadow Спасибо! Это было действительно полезно.
Нирал Мунджария

Это прекрасно работает с первой попытки. Но когда я удаляю искомый текст каким-то образом, следующий запрос занимает слишком много времени, чтобы ответить.
Садикша Гаутам

Это странно. Это все еще прекрасно работает на моей стороне. Не могли бы вы поделиться дополнительной информацией или, может быть, открыть новый вопрос для этого?
Просто тень

28

Непосредственно недоступен, как в 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

Здесь это для автозаполнения, но это работает во всех сценариях.


но есть ошибка от сервиса, он больше не запускается
Арун Тяги

Я не понимаю пример. [...] одностороннее целевое связывание. Почему контейнер может быть уведомлен valueChanges? не должно быть что-то. как (ngFormControl)="..."?
phil294

20

Вы можете создать в RxJS (V.6) Observable что делает все , что вам нравится.

view.component.html

<input type="text" (input)="onSearchChange($event.target.value)" />

view.component.ts

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


}

Спасибо, что помогло, однако я думаю, что импорт должен быть из rsjs/Rx, у меня были ошибки при использовании импорта, как вы написали ... так что в моем случае это сейчас:import { Observable } from 'rxjs/Rx';
ghiscoding

2
@ghiscoding Это зависит от версии rxjs. В 6 -й версии это: import { Observable } from 'rxjs';.
Матиас

Спасибо! Кроме того, вы можете просто использовать один pipeвызовpipe(debounceTime(300), distinctUntilChanged())
ал.

1
searchChangeObserver является подписчиком, поэтому searchChangeSubscriber будет более подходящим именем.
Khonsort

12

Для тех, кто использует lodash, очень легко отменить любую функцию:

changed = _.debounce(function() {
    console.log("name changed!");
}, 400);

затем просто добавьте что-то подобное в ваш шаблон:

<(input)="changed($event.target.value)" />

3
или просто (вход) = "изменено ($ event.target.value)"
Джейми Кудла

1
Спасибо за ответ с lodash :)
Vamsi

Я полагаю, что это по-прежнему будет вызывать обнаружение угловых изменений при каждом отдельном изменении, независимо от устранения ошибок.
AsGoodAsItGets

5

Решение с инициализацией абонента непосредственно в функции события:

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

Работает совершенно отлично для углового текстового поля автозаполнения. Большое спасибо.
Жасмин Актер Сума

4

Я решил это, написав декоратор debounce. Описанная проблема может быть решена путем применения @debounceAccessor к аксессору набора свойств.

Я также предоставил дополнительный декоратор debounce для методов, которые могут быть полезны в других случаях.

Это позволяет очень легко отменить свойство или метод. Параметр - это количество миллисекунд, которое должно длиться отладке, 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);
    }
  }
}

Я добавил дополнительный параметр для декоратора метода, который позволяет вам вызывать метод ПОСЛЕ задержки задержки. Я сделал это, чтобы, например, использовать его в сочетании с событиями при наведении курсора мыши или изменении размера, где я хотел, чтобы захват происходил в конце потока событий. В этом случае, однако, метод не будет возвращать значение.


3

Мы можем создать директиву [debounce], которая перезаписывает функцию viewToModelUpdate по умолчанию в ngModel пустой.

Директива Код

@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>

2

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

2

Простым решением было бы создать директиву, которую вы можете применить к любому элементу управления.

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>

Этот класс далек от компиляции Angular 7.
Стефан

1

Потратил на это часы, надеюсь, я смогу спасти кого-нибудь еще. Для меня следующий подход к использованию debounceна элементе управления является более интуитивным и легче для меня. Он основан на решении angular.io docs для автозаполнения, но с возможностью перехвата вызовов без необходимости привязывать данные к DOM.

Plunker

Сценарий использования для этого может быть проверка имени пользователя после его ввода, чтобы увидеть, если кто-то уже взял его, а затем предупреждение пользователя.

Примечание: не забывайте, (blur)="function(something.value)может иметь больше смысла для вас в зависимости от ваших потребностей.


1

DebounceTime в Angular 7 с RxJS v6

Ссылка на источник

Демо- ссылка

введите описание изображения здесь

В шаблоне 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)});
    }

    }

1

Вы также можете решить эту проблему с помощью декораторов, например, с помощью декоратора debounce из utils-decorator lib ( npm install utils-decorators):

import {debounce} from 'utils-decorators';

class MyAppComponent {

  @debounce(500)
  firstNameChanged($event, first) {
   ...
  }
}

0

Это лучшее решение, которое я нашел до сих пор. Обновления ngModelна 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.

Пример здесь: https://stackblitz.com/edit/ng2-debounce-working

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.