Create an analog clock using RxJS and Angular standalone components
Tags: rxjs, angular, typescript, tutorial
Published: 2023-02-18 14:05:48

##Introduction

This is day 2 of Wes Bos's JavaScript 30 challenge where I create an analog clock that displays the current time of the day. In the tutorial, I created the components using RxJS, custom operators, Angular standalone components and removed the NgModules.  

In this blog post, I describe how to create an observable that draws the hour, minute and second hands of an analog clock. The clock component creates a timer that emits every second to get the current time, calculates the rotation angle of the hands and set the CSS styles to perform line rotation.

##Create a new Angular project

```bash
ng generate application day2-ng-and-css-clock
```

##Bootstrap AppComponent

First, I convert `AppComponent` into standalone component such that I can bootstrap `AppComponent` and inject providers in main.ts.

```typescript
// app.component.ts

import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { ClockComponent } from './clock';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    ClockComponent
  ],
  template: '<app-clock></app-clock>',
  styles: [`
    :host {
      display: block;
    }
  `],
})
export class AppComponent {
  constructor(titleService: Title) {
    titleService.setTitle('Day 2 NG and CSS Clock');
  }
}
```

In Component decorator,  I put `standalone: true` to convert `AppComponent` into a standalone component.

Instead of importing `ClockComponent` in AppModule, I import `ClockComponent` (that is also a standalone component) in the imports array because the inline template references it. 

```typescript
// main.ts

import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

bootstrapApplication(AppComponent).catch(err => console.error(err));
```

Second,  I delete AppModule because it is not used anymore.

###Declare Clock component

I declare standalone component, `ClockComponent`, to create an analog clock.  To verify the component is a standalone, `standalone: true` is specified in the Component decorator.

```bash
src/app
├── app.component.ts
└── clock
    ├── clock.component.ts
    ├── clock.interface.ts
    ├── custom-operators
    │   └── clock.operator.ts
    └── index.ts
```

`clock-operators.ts` encapsulates two custom RxJS operators that help draw the clock hands of the analog clock. `currentTime` operator returns seconds, minutes and hours of the current time.  `rotateClockHands` receives the results of  currentTime and calculate the rotation angle of the hands.

```typescript
// clock.component.ts

import { AsyncPipe, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { timer } from 'rxjs';
import { currentTime, rotateClockHands } from './custom-operators/clock.operator';

@Component({
  selector: 'app-clock',
  standalone: true,
  imports: [
    AsyncPipe,
    NgIf,
  ],
  template: `
    <div class="clock" *ngIf="clockHandsTransform$ | async as clockHandsTransform">
      <div class="clock-face">
        <div class="hand hour-hand" [style.transform]="clockHandsTransform.hourHandTransform"></div>
        <div class="hand min-hand" [style.transform]="clockHandsTransform.minuteHandTransform"></div>
        <div class="hand second-hand" [style.transform]="clockHandsTransform.secondHandTransform"></div>
      </div>
    </div>
  `,
  styles: [...omitted due to brevity...],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ClockComponent {
  readonly oneSecond = 1000;

  clockHandsTransform$ = timer(0, this.oneSecond)
    .pipe(
      currentTime(),
      rotateClockHands(),
    );
}
```

`ClockComponent` imports `NgIf` and `AsyncPipe` because the components uses `ngIf` and `async` keywords to resolve `clockHandsTransform$` observable.  `clockHandsTransform$` is an observable that is consisted of the CSS styles to draw hour, minute and second hands. The observable is succinct because the `currentTime` and `rotateClockHands` custom operators encapsulate the logic.

###Create RxJS custom operators

It is a matter of taste but I prefer to refactor RxJS operators into custom operators when observable has many lines of code. For `clockHandsTransform$`, I refactor map into custom operators and reuse them in `ClockComponent`. 

```typescript
// clock.operator.ts

export function currentTime() {
    return map(() => { 
        const time = new Date();
        return { 
            seconds: time.getSeconds(),
            minutes: time.getMinutes(),
            hours: time.getHours()
        }
    });
}
```

`currentTime` operator gets the current time and calls the methods of the Date object to return the current second, minutes and hours.

```typescript
// clock.operator.ts

function rotateAngle (seconds: number, minutes: number, hours: number): HandTransformations { 
    const secondsDegrees = ((seconds / 60) * 360) + 90;
    const minsDegrees = ((minutes / 60) * 360) + ((seconds / 60) * 6) + 90;
    const hourDegrees = ((hours / 12) * 360) + ((minutes / 60) * 30) + 90;

    return { 
        secondHandTransform: `rotate(${secondsDegrees}deg)`,
        minuteHandTransform: `rotate(${minsDegrees}deg)`,
        hourHandTransform: `rotate(${hourDegrees}deg)`,
    }
}

export function rotateClockHands() {
    return function (source: Observable<{ seconds: number, minutes: number, hours: number }>) {
        return source.pipe(map(({ seconds, minutes, hours }) => rotateAngle(seconds, minutes, hours)));
    }
}
```

`currentTime` emits the results to `rotateClockHands` and the `rotateClockHands` operator invokes a helper function, `rotateAngle`, to derive the CSS styles of the hands.

Finally, I use both operators to compose clockHandsTransform$ observable.

###Use RxJS and Angular to implement observable in clock component

```typescript
// clock.component.ts
clockHandsTransform$ = timer(0, this.oneSecond)
    .pipe(
      currentTime(),
      rotateClockHands(),
    );
```

- timer(0, this.oneSecond) - emits an integer every second
- currentTime() - return the current second, minute and hour
- rotateClockHands() - calculate the rotation angle of second, minute and hour hands

This is it,  we have created a functional analog clock that displays the current time. 

##Final Thoughts

In this post, I show how to use RxJS and Angular standalone components to create an analog clock.  The application has the following characteristics after using Angular 15's new features: 

- The application does not have NgModules and constructor boilerplate codes.
- In ClockComponent, I import NgIf and AsyncPipe rather than CommonModule, only the minimum parts that the component requires.

This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.

##Resources:

- Github Repo: https://github.com/railsstudent/ng-rxjs-30/tree/main/projects/day2-ng-and-css-clock
- Live demo: https://railsstudent.github.io/ng-rxjs-30/day2-ng-and-css-clock/
- Wes Bos's JavaScript 30 Challenge: https://github.com/wesbos/JavaScript30