Angulaire et debout

Dans AngularJS, je peux décrocher un modèle en utilisant des options de modèle ng.

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

Comment décrocher un modèle en angulaire? J'ai essayé de chercher à décrocher dans les documents, mais je n'ai rien trouvé.

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

Une solution serait d'écrire ma propre fonction de rebondissement, par exemple:

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

Et mon html

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

Mais je cherche une fonction de construction, y a-t-il un sur Angular?

Mise à jour pour RC.5

Avec Angular 2, nous pouvons débuter en utilisant l'opérateur RxJS debounceTime debounceTime() sur la valueChanges de contrôle de 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(); ngOnInit() { // debounce keystroke events this.firstNameControl.valueChanges .debounceTime(1000) .subscribe(newValue => this.firstName = newValue); // throttle resize events 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'); } } 

Plunker

Le code ci-dessus comprend également un exemple de la façon d'accélérer les changements de taille des fenêtres, tel que demandé par @albanx dans un commentaire ci-dessous.


Bien que le code ci-dessus soit probablement le moyen angulaire de le faire, ce n'est pas efficace. Toutes les frappes de touche et tous les événements de redimensionnement, même s'ils sont débités et épuisés, entraînent une détection des changements en cours d'exécution. En d'autres termes, l' effondrement et l'étranglement ne modifient pas la fréquence de détection des changements . (J'ai trouvé un commentaire de GitHub par Tobias Bosch qui confirme cela.) Vous pouvez le voir lorsque vous exécutez le plunker et vous voyez combien de fois ngDoCheck() est appelé lorsque vous tapez dans la zone de saisie ou redimensionnez la fenêtre. (Utilisez le bouton bleu "x" pour exécuter le plunker dans une fenêtre séparée pour voir les événements de redimensionnement.)

Une technique plus efficace consiste à créer RxJS. Vous pouvez vous procurer des événements en dehors de la «zone» d'Angular. De cette façon, la détection de changement n'est pas appelée chaque fois qu'un événement se déclenche. Ensuite, dans vos méthodes de rappel de rappel, déclenchez manuellement la détection de changement – c'est-à-dire que vous contrôlez quand la détection de changement est appelée:

 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'; @ViewChild('input') inputElRef: ElementRef; constructor(private ngzone: NgZone, private cdref: ChangeDetectorRef, private appref: ApplicationRef) {} ngAfterViewInit() { this.ngzone.runOutsideAngular( () => { Observable.fromEvent(this.inputElRef.nativeElement, 'keyup') .debounceTime(1000) .subscribe(keyboardEvent => { this.firstName = keyboardEvent.target.value; this.cdref.detectChanges(); }); 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'); } } 

Plunker

J'utilise ngAfterViewInit() au lieu de ngOnInit() pour s'assurer que inputElRef est défini.

detectChanges() exécutera la détection de changement sur ce composant et ses enfants. Si vous préférez exécuter la détection de changement à partir du composant racine (c.-à-d. Exécuter une vérification de détection de changement complète), utilisez plutôt ApplicationRef.tick() . (Je fais un appel à ApplicationRef.tick() dans les commentaires dans le plunker.) Notez que l'appeler tick() entraînera l' ngDoCheck() .

Si vous ne voulez pas traiter avec @angular/forms , vous pouvez simplement utiliser un Subject RxJS avec des liens de changement.

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

Cela déclenche la détection de changement. Pour une façon qui ne déclenche pas la détection de changement, vérifiez la réponse de Mark.

Pas directement accessible comme dans angular1 mais vous pouvez facilement jouer avec NgFormControl et RxJS observables:

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

Cette publication du blog l'explique clairement: http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html

Ici, il s'agit d'un autocomplète, mais il fonctionne tous les scénarios.

J'ai éliminé un peu la directive de rebondissement

 import { Directive, Input, Output, EventEmitter } from '@angular/core'; import { NgControl } from '@angular/forms'; @Directive({ selector: '[ngModel][debounce]', }) export class DebounceDirective { @Output() public onDebounce = new EventEmitter<any>(); @Input('debounce') public debounceTime: number = 500; private isFirstChange: boolean = true; constructor(public model: NgControl) { } ngOnInit() { this.model.valueChanges .debounceTime(this.debounceTime) .distinctUntilChanged() .subscribe(modelValue => { if (this.isFirstChange) { this.isFirstChange = false; } else { this.onDebounce.emit(modelValue); } }); } } 

Utilisez-le comme

 <input [(ngModel)]="model" [debounce]="500" (onDebounce)="doSomethingWhenModelIsChanged()"> 

Une solution simple serait de créer une directive que vous pouvez appliquer à n'importe quel contrôle.

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

L'utilisation serait

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

Pour quiconque utilise lodash, il est extrêmement facile de rebondir toute fonction:

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

Ensuite, lancez simplement ceci dans votre modèle:

 <input [ngModel]="firstName" (ngModelChange)="changed()" /> 

J'ai résolu cela en écrivant un décorateur débardé. Le problème décrit pourrait être résolu en appliquant @debounceAccessor à l'accesseur défini de la propriété.

J'ai également fourni un décorateur de rebond supplémentaire pour les méthodes, ce qui peut être utile pour d'autres occasions.

Cela rend très facile de rebondir une propriété ou une méthode. Le paramètre est le nombre de millisecondes que le debout devrait durer, 100 ms dans l'exemple ci-dessous.

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

Et voici le code pour les décorateurs:

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

J'ai ajouté un paramètre supplémentaire pour le décorateur de méthode qui vous permet de déclencher la méthode APRÈS le délai de rebondissement. Je l'ai fait afin que je puisse, par exemple, l'utiliser lorsque couplé avec les événements de souris ou de redimensionnement, où je voulais que la capture se produise à la fin du flux d'événements. Dans ce cas cependant, la méthode ne renverra pas une valeur.

Vous pouvez créer un RxJS Observable qui fait ce que vous voulez.

View.component.html

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

View.component.ts

 import { Observable } from 'rxjs/Observable'; export class ViewComponent { searchChangeObserver; onSearchChange(searchValue: string) { if (!this.searchChangeObserver) { Observable.create(observer => { this.searchChangeObserver = observer; }).debounceTime(300) // wait 300ms after the last event before emitting last event .distinctUntilChanged() // only emit if value is different from previous value .subscribe(console.log); } this.searchChangeObserver.next(searchValue); } } 

Des heures passées, j'espère que je peux sauver quelqu'un d'autre quelque temps. Pour moi, l'approche suivante à l'utilisation d'un debounce sur un contrôle est plus intuitive et plus facile à comprendre pour moi. Il est construit sur la solution angular.io docs pour l'autocomplète mais avec la possibilité pour moi d'intercepter les appels sans devoir dépendre de la liaison des données au DOM.

Plunker

Un scénario de cas d'utilisation pour cela pourrait vérifier un nom d'utilisateur après avoir été tapé pour voir si quelqu'un l'a déjà pris, puis avertir l'utilisateur.

Remarque: n'oubliez pas, (blur)="function(something.value) pourrait avoir plus de sens pour vous en fonction de vos besoins.

Pour les formulaires réactifs et la manipulation sous Angular v2 (dernier) plus v4 regardez:

https://github.com/angular/angular/issues/6895#issuecomment-290892514

Espérons qu'il y aura un soutien natif pour ces types de choses bientôt …