The Scan & Reduce Operators in Angular applies an accumulator function on the values of the source observable. The Scan Operator returns all intermediate results of the accumulation, while Reduce only emits the last result. Both also use an optional seed value as the initial value.
Table of Contents
Scan in Angular
The scan
operator applies an accumulator function over the values emitted by the source Observableble sequentially and emits each value.
Syntax
1 2 3 | scan<T, R>(accumulator: (acc: R, value: T, index: number) => R, seed?: T | R): OperatorFunction<T, R> |
Where
accumulator
is the function, that is called on each source valueseed
is the initial accumulation value (optional)
The accumulator
function gets three argument.
acc
is the accumulator variable where the values are accumulated. value
comes from the source observable, index
of the value.
The initial value for the acc
comes from the seed
.
When the first value
arrives from the source, the scan operator invokes the accumulator function on these two variables and emits the result.
When the second value arrives from the source, the result of the previous step becomes the input ( acc
). The scan emits a new result, which then becomes the input for the third emission.
This cycle continues till the stream completes.
Scan Example
In the example below (acc, value) => acc + value
is the accumulator function. Seed is 0
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import { Component } from "@angular/core"; import { map, scan } from "rxjs/operators"; import { interval, of } from "rxjs"; @Component({ selector: "my-app", template: ` <h1>Scan Operator Example</h1> `, styleUrls: ["./app.component.css"] }) export class AppComponent { reTryCount = 0; ngOnInit() { of(1, 2, 3, 4, 5) .pipe(scan((acc, value) => acc + value, 0)) .subscribe( val => console.log(val), e => console.log(e), () => console.log("Complete") ); } } ***Console*** 1 3 6 10 15 Complete |
The value for acc
starts with the seed value i.e. 0
. The variable value
gets the value 1
, which is the first value emitted by the source. The scan operator runs the accumulator function (acc + value
= 1) and emits the result.
The result of the previous accumulator function becomes the input (acc
) of the next scan. It is added to the next value (i. e. 2) emitted from the source and the result becomes 3.
This will continue until we reach the end of sequence
In the following code, we change the seed to 10. Now the accumulator starts with 10 instead of 0.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | of(1, 2, 3, 4, 5) .pipe(scan((acc, value) => acc + value, 10)) .subscribe( val => console.log(val), e => console.log(e), () => console.log("Complete") ); *** Result *** 11 13 16 20 25 Complete |
Combining as Arrays
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | of(1, 2, 3, 4, 5) .pipe(scan((acc, value) => [...acc, value], [])) .subscribe( val => console.log(val), e => console.log(e), () => console.log("Complete") ); *** Console *** [1] [1, 2] [1, 2, 3] [1, 2, 3, 4] [1, 2, 3, 4, 5] |
Tracking Button Clicks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import { Component, ElementRef, ViewChild } from "@angular/core"; import { map, scan } from "rxjs/operators"; import { fromEvent, interval, of, Subscription } from "rxjs"; @Component({ selector: "my-app", template: ` <h1>Scan Operator Example</h1> <button #btn>Button</button> `, styleUrls: ["./app.component.css"] }) export class AppComponent { @ViewChild("btn", { static: true }) button: ElementRef; sub: Subscription; ngAfterViewInit() { this.sub = fromEvent(this.button.nativeElement, "click") .pipe(scan((acc, value) => acc + 1, 0)) .subscribe(val => console.log("You clicked " + val + " times")); } ngOnDestroy() { this.sub.unsubscribe(); } } |
Reduce in Angular
The Reduce
operator applies an accumulator function over the values emitted by the source Observation sequentially and returns the accumulated result when the source completes.
The Reduce operator behaves exactly like the scan operator, except for the following differences
- It does not return the intermediate results.
- It returns only after the source completes.
Reduce Example
The following example is similar to the scan example above. The only difference is that you will not see the intermediate results i.e. 1, 3, 6 10.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ngOnInit() { of(1, 2, 3, 4, 5) .pipe(reduce((acc, value) => acc + value, 0)) .subscribe( val => console.log(val), e => console.log(e), () => console.log("Complete") ); } ** Console ** 15 Complete |
Combining as Arrays
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ngOnInit() { of(1, 2, 3, 4, 5) .pipe(reduce((acc, value) => [...acc, value], [])) .subscribe( val => console.log(val), e => console.log(e), () => console.log("Complete") ); } ** Console *** [1, 2, 3, 4, 5] Complete |
Tracking Button Clicks
The Reduce operator emits only if the observable completes. Hence in the Tracking button click example, just replacing the scan with the reduce will not work.
In the following example, we create a new observable using the Subject and emit the click event using the event binding. This allows us to raise the complete notification.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | import { Component, ElementRef, ViewChild } from "@angular/core"; import { map, reduce, scan } from "rxjs/operators"; import { fromEvent, interval, of, Subject, Subscription } from "rxjs"; @Component({ selector: "my-app", template: ` <h1>Reduce Operator Example</h1> <button (click)="clickMe($event)">Click Me</button> <br /> <br /> <br /> <button (click)="startCounting($event)">Sart</button> <button (click)="stopCounting()">Stop</button> `, styleUrls: ["./app.component.css"] }) export class AppComponent { clickStream: Subject<Event>; sub: Subscription; ngOnInit() {} clickMe(event: Event) { console.log("Clicked"); if (this.clickStream) this.clickStream.next(event); } startCounting(event: Event) { this.clickStream = new Subject<Event>(); this.sub = this.clickStream .asObservable() .pipe(reduce((acc, value) => acc + 1, 0)) .subscribe(val => console.log("You clicked " + val + " times")); } stopCounting() { this.clickStream.complete(); } ngOnDestroy() {} } |
References
Read More