Implementing Smart Counter-An opportunity to understand RXJS operators much better

Angular&NodeEnthusiast
4 min readJul 21, 2020

--

Implementing a smart counter helped me understand a few RXJS operators like timer,takeUntil,merge etc much better.

So now what does this Smart Counter do? As name suggests it counts:) But what else? You can adjust 3 parameters:

  1. Initial Count Value
  2. The time gap at which count increments
  3. Rate at which the count increments

Below is the Component Template:

<label>Enter Speed</label>
<input type=”number” [(ngModel)]=”speed” placeholder=”Set Speed”>
<label>Enter Increase Rate</label>
<input type=”number” min=”1" [(ngModel)]=”rate” placeholder=”Set rate of increase”>
<label>Enter Initial Count</label>
<input class="form-control" type="number" min="0" (change)="setCounter($event)" placeholder="Set Initial Count Value">
<button id=”start”>Start</button>
<button id=”reset”>Reset</button>
<button id=”pause”>Pause</button>
<br>
<p id="count">{{counter}}</p>

As you can see above, we can adjust the speed(time gap at which the count increments),the rate at which count increments and also set the initial value of the counter in the textboxes.

We have buttons for Starting the counter,Reset the counter and Pause the counter.

Class:

export class SmartCounterComponent implements OnInit {constructor() { }
counter:number=0;
speed:number=1000;
rate:number=1;
pauseCounter$=new Subject<any>();
start$:Observable<any>;
reset$:Observable<any>;
pause$:Observable<any>
ngOnInit(): void {this.start$=fromEvent(document.querySelector(‘#start’),’click’).pipe(switchMap(()=>this.incrementCounter()))this.reset$=fromEvent(document.querySelector(‘#reset’),’click’).pipe(tap(()=>this.pauseCounter$.next()),mapTo(0));this.pause$=fromEvent(document.querySelector(‘#pause’),’click’).pipe(tap(()=>this.pauseCounter$.next()),map(x=>this.counter))merge(this.start$,this.reset$,this.pause$).subscribe(x=>this.counter=x);
}
setCounter(evt){
this.counter=Number.parseInt(evt.target.value);
}
incrementCounter(){
return timer(0,this.speed).pipe(
scan((acc,curr)=>acc+this.rate,this.counter),
takeUntil(this.pauseCounter$));
}
}

We have set the initial values for the rate as 1(min) and the time gap as 1000 ms.

speed:number=1000;
rate:number=1;

These variables are 2 way data bound as you can see below. As soon as I change them in the text boxes in the template, the updated rate,speed will automatically reflect in the class and vice-versa.

Also note that I dont want the rate by which the count increases to be less than 1, hence min=”1".

<label>Enter Speed</label>
<input type=”number” [(ngModel)]=”speed” placeholder=”Set Speed”>
<label>Enter Increase Rate</label>
<input type=”number” min=”1" [(ngModel)]=”rate” placeholder=”Set rate of increase”>

Now what about the initial counter value.

<label>Enter Initial Count</label>
<input class="form-control" type="number" min="0" (change)="setCounter($event)" placeholder="Set Initial Count Value">

The min initial count can be 0. Whenever I make a change in the textbox, the setCounter() is called which assigns the value to the counter variable. Its the counter variable that keeps getting updated when the counter starts,resets or pauses.

setCounter(evt){
this.counter=Number.parseInt(evt.target.value);
}

Ok now lets get to the piece of code that controls the Start,Pause and Reset functionality.

As you know we have 3 buttons for each of these functions. Whenever I click on any of these buttons,I am creating an observable using the fromEvent operator as below.

this.start$=fromEvent(document.querySelector(‘#start’),’click’).pipe(switchMap(()=>this.incrementCounter()))this.reset$=fromEvent(document.querySelector(‘#reset’),’click’).pipe(tap(()=>this.pauseCounter$.next()),mapTo(0));this.pause$=fromEvent(document.querySelector(‘#pause’),’click’).pipe(tap(()=>this.pauseCounter$.next()),map(x=>this.counter))merge(this.start$,this.reset$,this.pause$).subscribe(x=>this.counter=x);

I am merging all the 3 observables start$,reset$ and pause$ using merge operator because any value emitted by these observables have a direct impact on the counter value.Since all 3 observables are performing a common task of updating the counter, I have merged them.

start$ is the observable corresponding to a click event on the Start button. When I click on the button, I am calling the incrementCounter().

Note the use of switchMap operator. It comes in very useful, if I make multiple click requests at a time and I want to consider the latest one and discard the old ones if they are incomplete.

incrementCounter(){
return timer(0,this.speed).pipe(
scan((acc,curr)=>acc+this.rate,this.counter),
takeUntil(this.pauseCounter$));
}

In this method, we are using a timer operator that takes the speed variable set by the user in the template. Default value is 1000ms.

The timer operator will create an observable that emits numbers(0,1,2 and so on). The first value i.e 0 will be emitted immediately(after 0ms) and the subsequent values will be emitted with a time gap of speed set by the user.

The scan operator is needed because we need to increment the counter by a rate.

scan((acc,curr)=>acc+this.rate,this.counter)

acc is the accumulated value and curr is the current value emitted by the observable. We are not really bothered about the current value. Values emitted by the timer operator are not going to be used anywhere.

The accumulated value,initial counter value and the rate are important here. Let me show you how.

Say initial counter value =0, rate=2 and speed=1000. This means my counter should increment like this 0,2,4,6,8,10 and so on. The increment should happen with a gap of 1000ms.

The counter value i.e 0 is the initial value for acc. Every 1000ms, acc will be incremented by the rate i.e 2.

Finally we have the takeUntil operator. This operator is useful when you want to command to the observable to stop emitting values when specified condition is satisfied. Here the condition is that the observable created by timer should stop emitting values when the pauseCounter$ subject emits a value.

takeUntil(this.pauseCounter$)

This is important because I would want the counter to stop incrementing when the Pause button is clicked.

pause$ is the observable corresponding to a click event on the Pause button.When I click on the Pause button,I am doing 2 actions.

this.pause$=fromEvent(document.querySelector(‘#pause’),’click’).pipe(tap(()=>this.pauseCounter$.next()),map(x=>this.counter))

First one is I call next() on the pauseCounter$ subject so that the observable created by the timer operator stops emitting values.

A click event will create an observable that emits an event object. I really dont want this event object to be assigned to the counter. So the Second action is to map the event object to the current counter value. This means when I click on Pause, the counter will not increment and will remain the same value when it was stopped.

Finally reset$ is the observable corresponding to a click event on the Reset button. Again I am performing 2 actions here.

this.reset$=fromEvent(document.querySelector(‘#reset’),’click’).pipe(tap(()=>this.pauseCounter$.next()),mapTo(0));

First one is I call next() on the pauseCounter$ subject so that the observable created by the timer operator stops emitting values.

Second is to map the counter value to 0 i.e resetting the counter.

You can check the entire code below:

--

--

Angular&NodeEnthusiast
Angular&NodeEnthusiast

Written by Angular&NodeEnthusiast

Loves Angular and Node. I wish to target issues that front end developers struggle with the most.

No responses yet