angular度和反弹
在AngularJS中,我可以使用ng-model选项来删除模型。
ng-model-options="{ debounce: 1000 }"
我如何在Angular中删除一个模型? 我试图search文档中的debounce,但是我找不到任何东西。
https://angular.io/search/#stq=debounce&stp=1
一个解决办法是写我自己的去抖function,例如:
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)">
但是我正在寻找一个函数的构build,是否有一个在angular?
RC.5更新
使用Angular 2,我们可以使用RxJS运算符debounceTime debounceTime()
对表单控件的valueChanges
observable使用debounce:
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(); ngOnInit() { // debounce keystroke events this.firstNameControl.valueChanges .debounceTime(1000) .subscribe(newValue => this.firstName = newValue); // throttle resize events 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'); } }
Plunker
上面的代码还包含了一个如何调节窗口大小调整事件的示例,正如@albanx在下面的注释中提到的。
虽然上面的代码可能是这样做的angular度,但是效率不高。 每次按键和每个resize事件,即使它们被去抖和扼杀,都会导致更改检测运行。 换句话说, 去抖和限制不会影响更改检测运行的频率 。 (我发现Tobias Bosch的GitHub评论确认了这一点)。当你运行plunker时,你可以看到这个,你看到在input框中input或调整窗口的大小时,会调用ngDoCheck()
次数。 (使用蓝色的“x”button在单独的窗口中运行plunker以查看resize事件。)
一个更有效的技术是从Angular的“zone”之外的事件中自己创buildRxJS Observables。 这样,每次事件触发时都不会调用更改检测。 然后,在您的订阅callback方法中,手动触发更改检测 – 即您控制何时更改检测被调用:
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'; @ViewChild('input') inputElRef: ElementRef; constructor(private ngzone: NgZone, private cdref: ChangeDetectorRef, private appref: ApplicationRef) {} ngAfterViewInit() { this.ngzone.runOutsideAngular( () => { Observable.fromEvent(this.inputElRef.nativeElement, 'keyup') .debounceTime(1000) .subscribe(keyboardEvent => { this.firstName = keyboardEvent.target.value; this.cdref.detectChanges(); }); 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'); } }
Plunker
我使用ngAfterViewInit()
而不是ngOnInit()
来确保inputElRef
被定义。
detectChanges()
将对此组件及其子项运行更改检测。 如果您宁愿从根组件运行更改检测(即,运行完整更改检测检查ApplicationRef.tick()
改为使用ApplicationRef.tick()
。 (我在调用者的注释中调用ApplicationRef.tick()
。)注意,调用tick()
将导致调用ngDoCheck()
。
如果你不想处理@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); } }
这确实会触发更改检测。 对于不触发变化检测的方法,请查看Mark的答案。
不像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
这里是一个自动完成,但它适用于所有场景。
我清除了一些debounce指令
import { Directive, Input, Output, EventEmitter } from '@angular/core'; import { NgControl } from '@angular/forms'; @Directive({ selector: '[ngModel][debounce]', }) export class DebounceDirective { @Output() public onDebounce = new EventEmitter<any>(); @Input('debounce') public debounceTime: number = 500; private isFirstChange: boolean = true; constructor(public model: NgControl) { } ngOnInit() { this.model.valueChanges .debounceTime(this.debounceTime) .distinctUntilChanged() .subscribe(modelValue => { if (this.isFirstChange) { this.isFirstChange = false; } else { this.onDebounce.emit(modelValue); } }); } }
像使用它
<input [(ngModel)]="model" [debounce]="500" (onDebounce)="doSomethingWhenModelIsChanged()">
对于任何使用lodash的人来说,去除任何function都是非常容易的:
changed = _.debounce(function() { console.log("name changed!"); }, 400);
那么就把这样的东西扔到你的模板中:
<input [ngModel]="firstName" (ngModelChange)="changed()" />
简单的解决办法是创build一个指令,你可以应用于任何控制。
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装饰器来解决这个问题。 所描述的问题可以通过将@debounceAccessor应用到属性的set访问器来解决。
我还为方法提供了一个额外的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); } } }
我为方法装饰器添加了一个额外的参数,让您在触发延迟之后触发方法。 我这样做,所以我可以使用它,当鼠标hover或resize的事件,我希望捕获发生在事件stream的末尾。 但是,在这种情况下,该方法不会返回值。
你可以创build一个RxJS Observable来执行你喜欢的任何事情。
view.component.html
<input type="text" (input)="onSearchChange($event.target.value)" />
view.component.ts
import { Observable } from 'rxjs/Observable'; export class ViewComponent { searchChangeObserver; onSearchChange(searchValue: string) { if (!this.searchChangeObserver) { Observable.create(observer => { this.searchChangeObserver = observer; }).debounceTime(300) // wait 300ms after the last event before emitting last event .distinctUntilChanged() // only emit if value is different from previous value .subscribe(console.log); } this.searchChangeObserver.next(searchValue); } }
在这花了几个小时,希望我能救一些别人一些时间。 对我来说,在控件上使用debounce
的以下方法对我来说更直观,更易于理解。 它build立在用于自动完成的angular.io文档解决scheme上,但是能够拦截这些调用,而不必依赖于将数据绑定到DOM。
Plunker
一个用例场景可能是在input用户名后检查用户名,看是否有人已经使用它,然后警告用户。
注意:不要忘记, (blur)="function(something.value)
可能会更有意义,取决于您的需要。
对于Angular v2(最新版)加v4中的Reactive Forms和处理,请看:
https://github.com/angular/angular/issues/6895#issuecomment-290892514
希望很快会有这种东西的本地支持…