角度 2+ 和去抖动

2022-08-30 01:30:15

在AngularJS中,我能够通过使用ng模型选项来反析模型。

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

如何在Angular中取消模型?
我试图在文档中搜索debeounce,但我找不到任何东西。

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

一个解决方案是编写我自己的去抖函数,例如:

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

和我的网页

<input type=text [value]="firstName" #first (keyup)="firstNameChanged($event, first)">

但是我正在寻找一个内置函数,Angular中有一个吗?


答案 1

针对 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在下面的注释中所询问的那样。


尽管上面的代码可能是Angular的方式,但它效率不高。每次击键和每个调整大小事件(即使它们被取消和限制)都会导致更改检测运行。换句话说,取消抖动和限制不会影响更改检测运行的频率。(我发现Tobias Bosch的GitHub评论证实了这一点。当您运行 plunker 时,您可以看到这一点,并且当您在输入框中键入内容或调整窗口大小时,可以看到被调用的次数。(使用蓝色的“x”按钮在单独的窗口中运行 plunker 以查看调整大小事件。ngDoCheck()

一种更有效的技术是从Angular的“区域”之外的事件中自己创建RxJS Observables。这样,每次触发事件时都不会调用更改检测。然后,在订阅回调方法中,手动触发更改检测 , 即,您可以控制何时调用更改检测:

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()。(我在 plunker 的评论中打了个电话。请注意,调用将导致被调用。ApplicationRef.tick()tick()ngDoCheck()


答案 2

如果您不想处理 ,则可以只使用带有更改绑定的 RxJS 主题@angular/forms

视图组件.html

<input [ngModel]='model' (ngModelChange)='changed($event)' />

view.component.ts

import { Subject } from 'rxjs';
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 的答案。


更新

.pipe(debounceTime(300), distinctUntilChanged()) 是 rxjs 6 所需要的。

例:

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