Javascript 角度2+;和去盎司

Javascript 角度2+;和去盎司,javascript,angular,Javascript,Angular,在AngularJS中,我能够通过使用ng模型选项来消除模型的抖动 ng-model-options="{ debounce: 1000 }" 如何以角度对模型进行去抖动? 我试图在文档中搜索debounce,但什么也找不到 解决方案是编写我自己的去盎司函数,例如: import {Component, Template, bootstrap} from 'angular2/angular2'; // Annotation section @Component({

在AngularJS中,我能够通过使用ng模型选项来消除模型的抖动

ng-model-options="{ debounce: 1000 }"
如何以角度对模型进行去抖动?
我试图在文档中搜索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中有吗?

不像angular1中那样可以直接访问,但您可以轻松使用NgFormControl和RxJS observables:

<input type="text" [ngFormControl]="term"/>

this.items = this.term.valueChanges
  .debounceTime(400)
  .distinctUntilChanged()
  .switchMap(term => this.wikipediaService.search(term));

this.items=this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term=>this.wikipediaService.search(term));
这篇博文清楚地解释了这一点:


这是一个自动完成,但它适用于所有场景。

为RC.5更新

对于Angular 2,我们可以在表单控件的
valueChanges
observable:

从'@angular/core'导入{Component};
从'@angular/forms'导入{FormControl};
从“rxjs/Observable”导入{Observable};
导入'rxjs/add/operator/debounceTime';
导入'rxjs/add/operator/throttleTime';
导入“rxjs/add/observable/fromEvent”;
@组成部分({
选择器:“我的应用程序”,
模板:`

{{firstName}}` }) 导出类AppComponent{ firstName='Name'; firstNameControl=newformcontrol(); formCtrlSub:订阅; resizeSub:订阅; 恩戈尼尼特(){ //去盎司击键事件 this.formCtrlSub=this.firstNameControl.valueChanges .debounceTime(1000) .subscribe(newValue=>this.firstName=newValue); //油门调整事件 this.resizeSub=Observable.fromEvent(窗口“resize”) .节流时间(200) .订阅(e=>{ log('resize event',e); this.firstName+='*';//更改某些内容以显示其工作正常 }); } ngDoCheck(){console.log('change detection');} 恩贡德斯特罗(){ this.formCtrlSub.unsubscribe(); this.resizeSub.unsubscribe(); } }

上面的代码还包括一个如何限制窗口大小调整事件的示例,正如@albanx在下面的评论中所要求的那样


尽管上面的代码可能是一种有角度的方法,但它并不高效。每次击键和每次调整大小事件,即使它们被取消公告和限制,都会导致更改检测运行。换句话说,去抖动和节流不会影响更改检测运行的频率。(我找到了Tobias Bosch的一份报告证实了这一点。)运行plunker时可以看到这一点,并且可以看到在输入框中键入或调整窗口大小时调用了多少次
ngDoCheck()
。(使用蓝色“x”按钮在单独的窗口中运行plunker以查看调整大小事件。)

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

import{Component,NgZone,ChangeDetectorRef,ApplicationRef,
ViewChild,ElementRef}来自“@angular/core”;
从“rxjs/Observable”导入{Observable};
导入'rxjs/add/operator/debounceTime';
导入'rxjs/add/operator/throttleTime';
导入“rxjs/add/observable/fromEvent”;
@组成部分({
选择器:“我的应用程序”,
模板:`

{{firstName}}` }) 导出类AppComponent{ firstName='Name'; keyusub:订阅; resizeSub:订阅; @ViewChild('input')inputElRef:ElementRef; 构造函数(专用ngzone:ngzone,专用cdref:ChangeDetectorRef, 私有appref:ApplicationRef){} ngAfterViewInit(){ 此.ngzone.runOutsideAngular(()=>{ this.keyupSub=Observable.fromEvent(this.inputElRef.nativeElement,'keyup') .debounceTime(1000) .订阅(键盘事件=>{ this.firstName=keyboardEvent.target.value; this.cdref.detectChanges(); }); this.resizeSub=Observable.fromEvent(窗口“resize”) .节流时间(200) .订阅(e=>{ log('resize event',e); this.firstName+='*';//更改某些内容以显示其工作正常 this.cdref.detectChanges(); }); }); } ngDoCheck(){console.log('cd');} 恩贡德斯特罗(){ this.keyusub.unsubscribe(); this.resizeSub.unsubscribe(); } }

我使用
ngAfterViewInit()
而不是
ngOnInit()
来确保定义了
inputElRef


将对此组件及其子组件运行更改检测。如果您希望从根组件运行更改检测(即,运行完整的更改检测检查),则使用。(我在plunker的注释中调用了
ApplicationRef.tick()
)注意,调用
tick()
将导致调用
ngDoCheck()

为此花费了数小时,希望我能为其他人节省一些时间。对我来说,以下在控件上使用
debounce
的方法更直观,也更容易理解。它是基于angular.io docs解决方案为autocomplete构建的,但是我能够拦截调用,而不必依赖于将数据绑定到DOM

这方面的一个用例场景可能是在键入用户名后检查用户名,看看是否有人已经使用了它,然后警告用户


注意:别忘了,
(blur)=“function(something.value)
可能会根据您的需要对您更有意义。

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

view.component.html
简单的解决方案是创建一个可以应用于任何控件的指令

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);
                }
            });
    }
}
import{指令,ElementRef,输入,呈现器,主机侦听器,输出,事件
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);
    }
}
   constructor() {
        this.modelChanged.pipe(
            debounceTime(300), 
            distinctUntilChanged())
            .subscribe(model => this.model = model);
    }
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>
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);
    });
  }
} 
@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);
    }
  }
}
changed = _.debounce(function() {
    console.log("name changed!");
}, 400);
<(input)="changed($event.target.value)" />
<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);
  }  


}
@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, 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);
        });
    }
}
<input [(ngModel)]="hero.name" 
        [debounce]="3000" 
        (blur)="hero.name = $event.target.value"
        (ngModelChange)="onChange()"
        placeholder="name">
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
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)' />
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);
    }
}
<input type="text" (input)="onFind($event.target.value)">
<input [ngModel]="filterValue"
       (ngModelChange)="filterValue = $event ; search($event)"
        placeholder="Search..."/>
timer = null;
time = 250;
  search(searchStr : string) : void {
    clearTimeout(this.timer);
    this.timer = setTimeout(()=>{
      console.log(searchStr);
    }, time)
  }
<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)});
    }

    }
import {debounce} from 'utils-decorators';

class MyAppComponent {

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