在AngularJS中,我可以使用ng-model选项去抖模型。
ng-model-options="{ debounce: 1000 }"
如何在Angular中去抖模型?我试图在文档中搜索debounce,但我找不到任何东西。
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);
我的HTML
<input type=text [value]="firstName" #first (keyup)="firstNameChanged($event, first)">
但是我正在寻找一个内置函数,Angular中有一个吗?
答案 0 :(得分:175)
针对RC.5进行了更新
使用Angular 2,我们可以在表单控件的debounceTime()
可观察对象上使用RxJS运算符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在下面的评论中所述。
虽然上面的代码可能是Angular方式,但效率不高。每次击键和每次调整大小事件,即使它们被去抖动和限制,也会导致变化检测运行。换句话说,去抖动和限制不会影响更改检测的运行频率。 (我发现Tobias Bosch发现GitHub comment确认了这一点。)当您运行plunker时,您可以看到这一点,并且当您在输入框中键入或调整大小时,您会看到ngDoCheck()
被调用了多少次窗口。 (使用蓝色的“x”按钮在单独的窗口中运行plunker以查看调整大小事件。)
更有效的技术是在Angular的“区域”之外的事件中自己创建RxJS Observable。这样,每次事件触发时都不会调用更改检测。然后,在您的订阅回调方法中,手动触发更改检测 - 即,您控制何时调用更改检测:
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()
被调用。
答案 1 :(得分:111)
如果您不想处理@angular/forms
,则可以使用带有更改绑定的RxJS Subject
。
<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);
}
}
这会触发更改检测。 For a way that doesn't trigger change detection, check out Mark's answer.
.pipe(debounceTime(300), distinctUntilChanged())
。
示例:
constructor() {
this.modelChanged.pipe(
debounceTime(300),
distinctUntilChanged())
.subscribe(model => this.model = model);
}
答案 2 :(得分:29)
它可以作为指令实施
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);
});
}
}
答案 3 :(得分:28)
不能像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
此处是自动完成功能,但适用于所有情况。
答案 4 :(得分:16)
你可以创建一个RxJS(v.6)Observable,可以随心所欲。
<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);
}
}
答案 5 :(得分:11)
对于使用lodash的任何人来说,debounce任何功能都非常容易:
changed = _.debounce(function() {
console.log("name changed!");
}, 400);
然后将这样的内容扔进你的模板:
<input [ngModel]="firstName" (ngModelChange)="changed()" />
答案 6 :(得分:9)
由于该主题较旧,大多数答案在 Angular 6/7 上不起作用。
因此,这是使用RxJS的Angular 6+的简短解决方案。
首先导入必要的内容:
assert json_response(conn, 200)["data"] == [
%{
...
"published_at" => NaiveDateTime.to_iso8601(item.published_at),
...
}
]
在import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
上初始化:
ngOnInit
使用这种方式:
export class MyComponent implements OnInit, OnDestroy {
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();
}
}
P.S .:对于更复杂,更有效的解决方案,您可能仍然需要检查其他答案。
答案 7 :(得分:3)
我通过编写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);
}
}
}
我为方法装饰器添加了一个额外的参数,让你在去抖延迟之后触发方法。我这样做,所以我可以在使用鼠标悬停或调整大小事件时使用它,我想在事件流结束时进行捕获。但是,在这种情况下,该方法不会返回值。
答案 8 :(得分:3)
我们可以创建一个[debounce]指令,该指令用空的覆盖ngModel的默认viewToModelUpdate函数。
指令代码
@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>
答案 9 :(得分:2)
简单的解决方案是创建一个可以应用于任何控件的指令。
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>
答案 10 :(得分:2)
HTML文件:
#STM32F4XX
SET(CMSIS_DEVICE_HEADERS stm32f4xx.h system_stm32f4xx.h)
SET(CMSIS_DEVICE_SOURCES system_stm32f4xx.c)
SET(CMSIS_STARTUP_SOURCE startup_stm32f407xx.s)
FIND_PATH(CMSIS_DEVICE_INCLUDE_DIR ${CMSIS_DEVICE_HEADERS}
PATH_SUFFIXES include stm32${STM32_FAMILY_LOWER} cmsis
HINTS ${CMAKE_CURRENT_SOURCE_DIR}/Include/
CMAKE_FIND_ROOT_PATH_BOTH
)
include_directories(CMSIS_INCLUDE_DIRS)
FIND_FILE(CMSIS_${SRC_CLEAN}_FILE ${SRC}
PATH_SUFFIXES src stm32${STM32_FAMILY_LOWER} cmsis
HINTS ${CMAKE_CURRENT_SOURCE_DIR}/Source/Templates
CMAKE_FIND_ROOT_PATH_BOTH
)
LIST(APPEND CMSIS_SOURCES ${CMSIS_${SRC_CLEAN}_FILE})
FIND_FILE(CMSIS_STARTUP_SOURCE_FILE ${CMSIS_STARTUP_SOURCE}
PATH_SUFFIXES src stm32${STM32_FAMILY_LOWER} cmsis
HINTS ${CMAKE_CURRENT_SOURCE_DIR}/Source/Templates/gcc/
CMAKE_FIND_ROOT_PATH_BOTH
)
LIST(APPEND CMSIS_SOURCES ${CMSIS_STARTUP_SOURCE_FILE})
set(PUSH_SOURCE_HEADER_COMBINE
${CMSIS_SOURCES}
${CMSIS_INCLUDE_DIRS}
)
add_library(STATIC ${PUSH_SOURCE_HEADER_COMBINE})
TS文件:
<input [ngModel]="filterValue"
(ngModelChange)="filterValue = $event ; search($event)"
placeholder="Search..."/>
答案 11 :(得分:2)
您还可以使用装饰器来解决此问题,例如,使用utils-decorator lib(npm install utils-decorators
)中的去抖动装饰器来解决此问题:
import {debounce} from 'utils-decorators';
class MyAppComponent {
@debounce(500)
firstNameChanged($event, first) {
...
}
}
答案 12 :(得分:1)
花了很多时间,希望我可以节省一些时间。对我来说,在控件上使用debounce
的以下方法对我来说更直观,更容易理解。它建立在angular.io docs解决方案的基础上,用于自动完成,但能够拦截调用,而不必依赖于将数据绑定到DOM。
用例方案可能是在输入用户名后检查用户是否已经接受用户名,然后警告用户。
注意:不要忘记,(blur)="function(something.value)
根据您的需要可能对您更有意义。
答案 13 :(得分:1)
使用RxJS v6在Angular 7中的DebounceTime
来源Link
演示Link
在HTML模板中
<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)});
}
}
答案 14 :(得分:0)
这是我迄今为止找到的最佳解决方案。更新ngModel
和blur
debounce
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);
});
}
}
借鉴https://stackoverflow.com/a/47823960/3955513
然后在HTML中:
<input [(ngModel)]="hero.name"
[debounce]="3000"
(blur)="hero.name = $event.target.value"
(ngModelChange)="onChange()"
placeholder="name">
在blur
上,使用普通的javascript明确更新模型。
答案 15 :(得分:0)
直接在事件函数中使用初始化订户的解决方案:
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);
}
}
和html:
<input type="text" (input)="onFind($event.target.value)">