Угловые и дребезга
в 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)">
но я ищу встроенную функцию, есть ли она в угловом?
13 ответов:
обновлено для RC.5
С угловой 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(); } }приведенный выше код также включает в себя пример того, как дросселировать события изменения размера окна, как задано @albanx в комментарии ниже.
хотя приведенный выше код, вероятно, является угловым способом его выполнения, он не эффективен. Каждое нажатие клавиши и каждое событие изменения размера, даже если они отменяются и регулируются, приводит к запуску обнаружения изменений. Другими словами,отключение и дросселирование не влияют на то, как часто выполняется обнаружение изменений. (Я нашел GitHub комментарий Тобиас Бош это подтверждает.) Вы можете увидеть это, когда вы запускаете планкер, и вы видите, сколько раз
ngDoCheck()вызывается при вводе в поле ввода или изменении размера окна. (Используйте синюю кнопку " x " для запуска plunker в отдельном окне, чтобы увидеть события resize.)более эффективным методом является создание RxJS наблюдаемых самостоятельно из событий, вне "зоны"углового. Таким образом, обнаружение изменений не вызывается каждый раз при возникновении события. Затем в ваших методах обратного вызова subscribe вручную запускайте обнаружение изменений-т. е. вы контролируете, когда обнаружение изменений вызывается:
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(); } }я использую
ngAfterViewInit()вместо изngOnInit()обеспечитьinputElRefопределяется.
detectChanges()запускает обнаружение изменений на этом компоненте и его дочерних элементах. Если вы предпочитаете запускать обнаружение изменений из корневого компонента (т. е. выполнять полную проверку обнаружения изменений), то используйтеApplicationRef.tick(). (Я позвонил вApplicationRef.tick()в комментариях в plunker.) Обратите внимание, что вызовtick()вызываетngDoCheck()называться.
если вы не хотите иметь дело с
@angular/forms, вы можете просто использовать RxJSSubjectс привязками изменения.вид.деталь.HTML-код
<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); } }это вызывает обнаружение изменений. способ, который не вызывает обнаружение изменений, проверьте ответ Марка.
не доступны напрямую, как в angular1, но вы можете легко играть с ngformcontrol и RxJS observables:
<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
здесь это для автозаполнения, но он работает во всех сценариях.
Это может быть реализовано как Директива
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 'rxjs/add/operator/takeUntil'; @Directive({ selector: '[ngModel][debounce]', }) export class DebounceDirective implements OnInit, OnDestroy { @Output() public onDebounce = new EventEmitter<any>(); @Input('debounce') public debounceTime: number = 500; private isFirstChange: boolean = true; private ngUnsubscribe: Subject<void> = new Subject<void>(); constructor(public model: NgControl) { } ngOnInit() { this.model.valueChanges .takeUntil(this.ngUnsubscribe) .debounceTime(this.debounceTime) .distinctUntilChanged() .subscribe(modelValue => { if (this.isFirstChange) { this.isFirstChange = false; } else { this.onDebounce.emit(modelValue); } }); } ngOnDestroy() { this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); } }использовать его как
<input [(ngModel)]="model" [debounce]="500" (onDebounce)="doSomethingWhenModelIsChanged()">
Вы можете создать an RxJS (V. 6) наблюдаемых это делает все, что вам нравится.
вид.деталь.HTML-код
<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, это очень легко debounce функции:
changed = _.debounce(function() { console.log("name changed!"); }, 400);тогда просто бросьте что-то вроде этого в свой шаблон:
<input [ngModel]="firstName" (ngModelChange)="changed()" />
Я решил эту проблему, написав декоратор дребезга. Описанная проблема может быть решена путем применения @debounceAccessor к свойству set accessor.
Я также поставил дополнительный декоратор 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); } } }я добавил дополнительный параметр для декоратора метода, который позволяет вам запускать метод после задержки debounce. Я сделал это, чтобы я мог, например, использовать его в сочетании с наведением курсора мыши или изменением размера событий, где я хотел, чтобы захват происходил в конце потока событий. Однако в этом случае метод не возвращает значение.
мы можем создать директиву [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>
простым решением было бы создать директиву, которую можно применить к любому элементу управления.
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)может сделать больше смысла для вас в зависимости от ваших потребностей.
это лучшее решение я нашел до сих пор. Обновляет
ngModelonblurиdebounceimport { 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">On
blurмодель явно обновляется с помощью простого javascript.пример здесь:https://stackblitz.com/edit/ng2-debounce-working
так как тема старая, большинство ответов не работают on Угловое 6.
Итак, вот короткое и простое решение для Angular 6 с RxJS.сначала импортируйте необходимые вещи:
import { Component, OnInit } from '@angular/core'; import { Subject } from 'rxjs'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators';инициализировать на
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); }); } }использовать таким образом:
<input [ngModel]='notesText' (ngModelChange)='notesModelChanged.next($event)' />P. S.: Для более сложных и эффективных решений, вы можете все еще хотите, чтобы проверить другие ответы.
для реактивных форм и обработки под угловым v2 (последний) плюс v4 посмотрите на:
https://github.com/angular/angular/issues/6895#issuecomment-290892514
надеюсь, что скоро будет встроенная поддержка для таких вещей...
Comments