Develop Counter component with Angular 7 and RxJS

Standard
Spread the love
  • 1
    Share

The strong benefit of using Angular framework is the emphasis on component driven development and thereby promoting reusability. On the other hand with the help of RxJS the complex tasks can be easily achieved. The fact that RxJS heavily uses chained API approach helps to mix and match it’s APIs to achieve complex functionality.

Sandclock on rocks
Photo by Aron Visuals on Unsplash

The beginning

I was looking for a counter component in Angular. The primary requirements I had

  • Input total number of seconds
  • API to start/stop counter
  • Notification after counter reaches zero
  • Formatted output e.g. mm:ss (Where mm stands for minutes and ss stands for seconds)

The requirements were not that complex and so I thought of developing my own component. The process started with identifying the right approach for developing this component.

Identify Input and Output

This part was pretty simple. I already knew what I expected from the counter and the data that I can feed to it. Based on the list defined above, I started defining the counter component

@Component({
  selector: 'app-counter',
  templateUrl: './counter.component.html',
  styleUrls: ['./counter.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent implements OnInit {
@Input()
startAt = 1;

@Input()
showTimeRemaining = true;

@Output()
counterState = new EventEmitter();

constructor(private changeDetector: ChangeDetectorRef) { }

}

If you are already familiar with Angular, most of the things written above must be known to you. @Input defines values that you can pass to the component and @Output is used to provide meaningful information to calling routine.

The interesting part is @Component, which uses ChangeDetectionStrategy. Instead of relying on default implementation, we know that the component should be updated at regular intervals rather than leaving it at the mercy of Angular’s default change detection strategy. This is also a recommended practice to improve performance and reliability of the component.

API – start and stop

We need explicit APIs to start a counter. Also in case if we wish to stop the counter, there has to be an equivalent method. So I added following two methods


public start() {
    this.currentValue = this.formatValue(this.startAt);
    this.changeDetector.detectChanges();

    const t: Observable<number> = interval(1000);
    this.currentSubscription = t.pipe(take(this.startAt)).map(v =&gt; this.startAt - (v + 1)).subscribe(v =&gt; {
        this.currentValue = this.formatValue(v);
        this.changeDetector.detectChanges();
      }, err =&gt; {
        this.counterState.error(err);
      }, () =&gt; {
        this.currentSubscription.unsubscribe();
        this.currentValue = '00:00';
        this.counterState.emit('COMPLETE');
        this.changeDetector.detectChanges();
      });
  }

private formatValue(v) {
const minutes = Math.floor(v / 60);
        const formattedMinutes = '' + (minutes &gt; 9 ? minutes : '0' + minutes);
        const seconds = v % 60;
        const formattedSeconds = '' + (seconds &gt; 9 ? seconds : '0' + seconds);

return `${formattedMinutes}:${formattedSeconds}`;
}

As soon as the start method is called, we set the initial value of the counter to the startAt variable. This shows total time.

RxJS in action

I am making use of RxJS’s interval function to track the timelapse. The beauty of RxJS is the ability to chain the API calls. I am making use of interval which is somewhat similar to setInterval method in JavaScript. But this method returns you an Observable which can be then plugged with some of the interesting RxJS APIs. Here is the list of other functions we have used

  • take – As defined in the documentation, it is used to accept only the first n values. In our case, the maximum value is defined using startAt (Note that the counter shows the time remaining, rather than time elapsed.)
  • map – Useful to transform value received from take. The value received from take is reduced from Total time.
  • subscribe – Finally, we need the computed value to display in Counter. The subscribe method has 3 blocks. The first block gets called every time the value is computed by map. The second err block is called when there is an exception. The third block is called when the counter reaches to 0.

Subscribe

The First block of subscribe constantly updates the value to be displayed in Counter. The value is passed to a formatter function and assigned to a variable. This variable is referenced in the component’s template and the change in value is informed using the ChangeDetector. Refer to following HTML code for Counter.

<div class="counter-parent">
    <span>{{currentValue}}</span>
</div>

The complete block of subscribe ensures the cleanup activities after the counter value is exhausted. The block also emits a state to indicate that the counter has reached to the zero. This is useful to perform any actions based on timed activities. We then invoke ChangeDetector again to refresh the Counter’s appearance.

As stated, we also need a stop method to explicitly stop the Counter. This is useful in a scenario wherein a user is done with the time bound activity and wish to move to the next one before the time elapses.


public stop() {
    this.currentSubscription.unsubscribe();
    this.counterState.emit('ABORTED');
}

We are done with coding our counter component. The takeaway from this implementation is to develop a component which can emit a value and keep the application informed about it’s state. This reduces mingling of component APIs with each other and reuse them wherever required.

Demo

Check this stackblitz link for complete source code and Demo.

Leave a Reply

Your email address will not be published. Required fields are marked *