在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中有吗?
我通过编写一个debounce decorator来解决这个问题。所描述的问题可以通过将@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);
}
}
}
我为方法装饰器添加了一个额外的参数,让你在debounce延迟后触发方法。我这样做是为了在与鼠标悬停或调整事件相结合时使用它,我希望捕获发生在事件流的末尾。但是,在这种情况下,该方法不会返回值。
RC.5更新
在Angular 2中,我们可以使用RxJS操作符debounceTime()对表单控件的valueChanges可观察对象进行deboundound:
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在下面的评论中所问的那样。
Although the above code is probably the Angular-way of doing it, it is not efficient. Every keystroke and every resize event, even though they are debounced and throttled, results in change detection running. In other words, debouncing and throttling do not affect how often change detection runs. (I found a GitHub comment by Tobias Bosch that confirms this.) You can see this when you run the plunker and you see how many times ngDoCheck() is being called when you type into the input box or resize the window. (Use the blue "x" button to run the plunker in a separate window to see the resize events.)
一个更有效的方法是你自己从事件中创建RxJS的可观察对象,在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();
}
}
砰砰作响
我使用ngAfterViewInit()而不是ngOnInit()来确保inputElRef被定义。
detectChanges()将在该组件及其子组件上运行变更检测。如果您更愿意从根组件运行变更检测(即,运行完整的变更检测检查),则使用ApplicationRef.tick()代替。(我在plunker的注释中调用ApplicationRef.tick()。)注意,调用tick()将导致ngDoCheck()被调用。