Dado:
Un efecto NgRx que maneja una solicitud para "acercar" una pantalla. Recibe una notificación cada vez que los usuarios hacen clic en un botón apropiado.
public readonly zoomIn$ = createEffect( () => this.actions$.pipe( ofType(zoomIn), tap(() => { scale(); }), ), { dispatch: false }, ); Nota: La acción zoomIn no pudo y no contiene ninguna carga útil. Considéralo solo como un disparador
Problema:
El redibujado cuesta recursos y en algunos casos toma unos segundos para obtener una nueva escala. Entonces, si desea escalar varias veces seguidas, se verá obligado a esperar.
Solución:
Al usar el operador debounceTime , posponga la llamada de la función scale() y espere hasta que los usuarios hagan varios clics. Suena bien. El único problema es que debounceTime nos notifica el último valor. Y lo que necesitamos es una cantidad de valores (clics del usuario) silenciados por el operador debounceTime .
En una vista más general, la tarea suena como: ¿cómo calcular el recuento de valores emitidos por el flujo de origen y silenciados por el operador debounceTime ?
Mi solución es crear un operador canalizable personalizado que logre el objetivo.
¿Qué necesitamos? Por supuesto, necesitamos un operador debounceTime .
.pipe( debounceTime(300), ) Luego debemos calcular el número de valores que se han emitido. Hay un operador de scan que se parece bastante a una conocida función de reduce . Le daremos un valor inicial y aumentaremos un contador en cada valor recibido del flujo de origen. Tenemos que colocarlo antes del operador debounceTime . Ahora parece una secuencia de índices.
.pipe( scan(acc => acc + 1, 0), debounceTime(300), ) Cuando debounceTime nos notifica el último índice, ¿cómo podemos saber la cantidad de valores silenciados? Tenemos que compararlo con el índice anterior que se ha emitido. El valor anterior se puede recibir utilizando un operador por pairwise . Y luego obtenga una diferencia entre ellos usando el operador de map .
.pipe( scan(acc => acc + 1, 0), debounceTime(300), pairwise(), map(([previous, current]) => current - previous), ) Si intenta esto en el estado actual, notará que algo anda mal, que no funciona por primera vez. El problema radica en el operador pairwise . Emite pares de valores (anterior y actual), por lo que espera a tener al menos dos valores antes de iniciar la emisión de pares. ¿Es justo? ¿Sí, lo es? Es por eso que necesitamos hacer un poco de trampa y proporcionar un primer valor (es decir, 0), con el uso del operador startWith .
La implementación final
/** * Emits a notification from the source Observable only after a particular time span has passed without another source emission, * with an exact number of values were emitted during that time. * * @param dueTime the timeout duration in milliseconds for the window of time required to wait for emission silence before emitting the most recent source value. * @returns MonoTypeOperatorFunction */ export const debounceCounter = (dueTime: number): MonoTypeOperatorFunction<number> => (source: Observable<unknown>): Observable<number> => new Observable(observer => source .pipe( scan(acc => acc + 1, 0), debounceTime(dueTime), startWith(0), pairwise(), map(([previous, current]) => current - previous), ) .subscribe({ next: x => { observer.next(x); }, error: err => { observer.error(err); }, complete: () => { observer.complete(); }, }), );ejemplo de uso
public readonly zoomIn$ = createEffect( () => this.actions$.pipe( ofType(zoomIn), debounceCounter(300), tap(times => { // scale n-times }), ), { dispatch: false }, );