Counter component with Angular

Counter component with Angular

The strong benefit of using Angular framework is the emphasis on component-driven development, 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.


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

  • A total number of seconds.
  • API to start/stop counter
  • Notification after the 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: 'counter',
  template: `<div class="counter-parent">
    <span>{{currentValue}}</span>
</div>`,
  styles: [`h1 { font-family: Lato; }`],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent  {
  @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 the 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 the performance and reliability of the component.

Video version of the article

API - start and stop

We need explicit APIs to start a counter. Also in case we wish to stop the counter, there has to be an equivalent method. So I added the 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 => this.startAt - (v + 1)).subscribe(v => {
        this.currentValue = this.formatValue(v);
        this.changeDetector.detectChanges();
      }, err => {
        this.counterState.error(err);
      }, () => {
        this.currentSubscription.unsubscribe();
        this.currentValue = '00:00';
        this.counterState.emit('COMPLETE');
        this.changeDetector.detectChanges();
      });
  }

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

RxJS in action

I am making use of RxJS's interval function to track the time-lapse. The beauty of RxJS is the ability to chain API calls. I am making use of interval which is somewhat similar to the 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 the 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 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 the 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 wishes to move to the next one before the time elapses.

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