tutorial examples ejemplos javascript angular

javascript - examples - Angular y antirrebote



angularjs tutorial (15)

En AngularJS puedo eliminar el rebote de un modelo usando las opciones de ng-model.

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

¿Cómo puedo eliminar el rebote de un modelo en angular? Traté de buscar rebote en los documentos pero no pude encontrar nada.

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

Una solución sería escribir mi propia función antirrebote, por ejemplo:

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

Y mi html

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

Pero estoy buscando una función incorporada, ¿hay alguna en Angular?


Archivo HTML:

<input [ngModel]="filterValue" (ngModelChange)="filterValue = $event ; search($event)" placeholder="Search..."/>

Archivo TS:

timer = null; time = 250; search(searchStr : string) : void { clearTimeout(this.timer); this.timer = setTimeout(()=>{ console.log(searchStr); }, time) }


Como el tema es antiguo, la mayoría de las respuestas no funcionan en Angular 6/7/8 .
Así que aquí hay una solución corta y simple para Angular 6+ con RxJS.

Importar cosas necesarias primero:

<input type="text" (input)="onSearchChange($event.target.value)" />

Inicializar en ngOnInit :

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

Use de esta manera:

import { Component, OnInit, OnDestroy } from ''@angular/core''; import { Subject, Subscription } from ''rxjs''; import { debounceTime, distinctUntilChanged } from ''rxjs/operators'';

PD: para soluciones más complejas y eficientes, es posible que desee consultar otras respuestas.


Esta es la mejor solución que he encontrado hasta ahora. Actualiza ngModel en blur y 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); }); } }

prestado de https://.com/a/47823960/3955513

Luego en HTML:

<input [(ngModel)]="hero.name" [debounce]="3000" (blur)="hero.name = $event.target.value" (ngModelChange)="onChange()" placeholder="name">

En el blur el modelo se actualiza explícitamente usando JavaScript simple.

Ejemplo aquí: https://stackblitz.com/edit/ng2-debounce-working


No accesible directamente como en angular1 pero puedes jugar fácilmente con NgFormControl y observables RxJS:

<input type="text" [ngFormControl]="term"/> this.items = this.term.valueChanges .debounceTime(400) .distinctUntilChanged() .switchMap(term => this.wikipediaService.search(term));

Esta publicación de blog lo explica claramente: http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html

Aquí es para un autocompletado, pero funciona en todos los escenarios.


Para cualquiera que use lodash, es extremadamente fácil debounce cualquier función:

changed = _.debounce(function() { console.log("name changed!"); }, 400);

entonces simplemente arroje algo como esto en su plantilla:

<(input)="changed($event.target.value)" />


Pasé horas en esto, espero poder salvar a alguien más en algún momento. Para mí, el siguiente enfoque para usar debounce en un control es más intuitivo y más fácil de entender para mí. Se basa en la solución angular.io docs para autocompletar, pero con la capacidad de interceptar las llamadas sin tener que depender de vincular los datos al DOM.

Plunker

Un escenario de caso de uso para esto podría ser verificar un nombre de usuario después de escribirlo para ver si alguien ya lo ha tomado, luego advertir al usuario.

Nota: no lo olvide, (blur)="function(something.value) podría tener más sentido para usted dependiendo de sus necesidades.


Podemos crear una directiva [debounce] que sobrescribe la función viewToModelUpdate predeterminada de ngModel con una vacía.

Código de directiva

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

Cómo usarlo

<div class="ui input"> <input debounce [delay]=500 [(ngModel)]="myData" type="text"> </div>


Podría implementarse como Directiva

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

úsalo como

<input [(ngModel)]="value" (onDebounce)="doSomethingWhenModelIsChanged($event)">

muestra componente

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


Puede crear un Observable RxJS (v.6) que haga lo que quiera.

view.component.html

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

view.component.ts

<input [ngModel]=''notesText'' (ngModelChange)=''notesModelChanged.next($event)'' />


Resolví esto escribiendo un decorador antirrebote. El problema descrito podría resolverse aplicando @debounceAccessor al conjunto de acceso de la propiedad.

También he proporcionado un decorador de rebote adicional para métodos, que puede ser útil para otras ocasiones.

Esto hace que sea muy fácil eliminar el rebote de una propiedad o un método. El parámetro es el número de milisegundos que debe durar el rebote, 100 ms en el siguiente ejemplo.

@debounceAccessor(100) set myProperty(value) { this._myProperty = value; } @debounceMethod(100) myMethod (a, b, c) { let d = a + b + c; return d; }

Y aquí está el código para los decoradores:

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

Agregué un parámetro adicional para el decorador de métodos que le permite activar el método DESPUÉS del retraso de rebote. Lo hice para poder usarlo, por ejemplo, cuando se combina con mouseover o cambiar el tamaño de los eventos, donde quería que la captura ocurriera al final de la secuencia de eventos. Sin embargo, en este caso, el método no devolverá un valor.


Si no desea lidiar con @angular/forms , puede usar un Subject RxJS con enlaces de cambio.

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

Esto activa la detección de cambios. Para una forma que no active la detección de cambios, consulte la respuesta de Mark.

Actualizar

.pipe(debounceTime(300), distinctUntilChanged()) es necesario para rxjs 6.

Ejemplo:

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


Solución con suscriptor de inicialización directamente en función de evento:

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

Y html:

<input type="text" (input)="onFind($event.target.value)">


Una solución simple sería crear una directiva que pueda aplicar a cualquier control.

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

el uso sería

<textarea [ngModel]="somevalue" [debounce]="2000" (onDebounce)="somevalue = $event" rows="3"> </textarea>


Actualizado para RC.5

Con Angular 2 podemos eliminar el rebote usando el operador debounceTime() en el valor de un control de formulario valueChanges observables:

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

El código anterior también incluye un ejemplo de cómo regular los eventos de cambio de tamaño de ventana, como lo solicitó @albanx en un comentario a continuación.

Aunque el código anterior es probablemente la forma angular de hacerlo, no es eficiente. Cada pulsación de tecla y cada evento de cambio de tamaño, a pesar de que se eliminan y se estrangulan, da como resultado la detección de cambios en ejecución. En otras palabras, la eliminación de rebotes y la limitación no afectan la frecuencia con la que se ejecuta la detección de cambios . (Encontré un comentario de GitHub de Tobias Bosch que confirma esto). Puedes ver esto cuando ejecutas el plunker y ves cuántas veces se ngDoCheck() cuando ngDoCheck() en el cuadro de entrada o cambias el tamaño de la ventana. (Use el botón azul "x" para ejecutar el plunker en una ventana separada para ver los eventos de cambio de tamaño).

Una técnica más eficiente es crear RxJS Observables usted mismo a partir de los eventos, fuera de la "zona" de Angular. De esta manera, la detección de cambios no se llama cada vez que se dispara un evento. Luego, en sus métodos de devolución de llamada de suscripción, active manualmente la detección de cambios, es decir, usted controla cuándo se llama a la detección de cambios:

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

Uso ngAfterViewInit() lugar de ngOnInit() para asegurar que inputElRef esté definido.

detectChanges() ejecutará la detección de cambios en este componente y sus hijos. Si prefiere ejecutar la detección de cambios desde el componente raíz (es decir, ejecutar una verificación de detección de cambios completa), utilice en su lugar ApplicationRef.tick() . (Puse una llamada a ApplicationRef.tick() en los comentarios en el plunker.) Tenga en cuenta que llamar a tick() hará que se ngDoCheck() .


DebounceTime en Angular 7 con RxJS v6

freakyjolly.com/… fuente

Link demostración

En plantilla HTML

<input type="text" #movieSearchInput class="form-control" placeholder="Type any movie name" [(ngModel)]="searchTermModel" />

En componente

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