Countdowns. They can be useful for lots of reasons.

You can use them, for example, on an ecommerce site if you're running a sale that expires in a few hours or days.

You can also use them in quizzes You'll do that when you want to give users a limited amount of time to answer.

Here, I'll show you how to create a timer that displays how much time is left before a meeting, appointment, or other activity.

It's pretty easy to implement and when it's finished, you'll have something that looks like this:

 

That red arrow points to a countdown clock that will update continuously. Although in this case it's set to update every minute, you could easily adapt the code I'll show you here to update it every second.

The Business Requirements

Your boss Smithers walks into your office with a bottle of Tequila. It's Cinco de Mayo, after all.

"You need a countdown timer!" he yells with a slur that makes it abundantly clear he's been day-drinking.

"On that CRM app," he continues. "You show the date of activities but you don't show how long until they happen."

He pauses awkwardly.

"Also, if the activity or task is overdue, make sure the user knows it!"

He stumbles out of your office.

Laying Pipe

Start with a pipe.

Why? Because the whole point of this exercise is to transform output. And that's exactly what an Angular pipe does.

You've probably used pipes in the past to format dates, numbers, or currencies. But here, you're going to need a custom pipe.

Fortunately, it's easy to develop a custom pipe.

Here's the code that makes the magic happen:

import { Pipe, PipeTransform } from '@angular/core';

const MILLISECONDS_IN_SECOND: number = 1000;
const MILLISECONDS_IN_MINUTE: number = 60 * MILLISECONDS_IN_SECOND;
const MILLISECONDS_IN_HOUR: number = MILLISECONDS_IN_MINUTE * 60;
const MILLISECONDS_IN_DAY: number = MILLISECONDS_IN_HOUR * 24;
const SINGLE_DAY: string = 'day';
const PLURAL_DAY: string = 'days'
const SINGLE_HOUR: string = 'hour';
const PLURAL_HOUR: string = 'hours';
const SINGLE_MINUTE: string = 'minute';
const PLURAL_MINUTE: string = 'minutes';


@Pipe({
  name: 'timeDifference'
})
export class TimeDifferencePipe implements PipeTransform {
  transform(value: number): string {
    let display: string = "calculating...";
    let tense: string = 'from now';

    if (value) {
      if (value < 0) {
        tense = 'ago';
        value = Math.abs(value);
      }

      if (value < MILLISECONDS_IN_MINUTE) {
        display = `less than a minute ${tense}`;
      } else if (value < MILLISECONDS_IN_HOUR) {
        let minutes: number = Math.floor(value / MILLISECONDS_IN_MINUTE);
        let units: string = (minutes == 1) ? SINGLE_MINUTE : PLURAL_MINUTE;
        display = `${minutes} ${units} ${tense}`;
      } else if (value < MILLISECONDS_IN_DAY) {
        let hours: number = Math.floor(value / MILLISECONDS_IN_HOUR);
        let minutes: number = (Math.floor((value - (hours * MILLISECONDS_IN_HOUR)) / MILLISECONDS_IN_MINUTE));
        let hoursUnits: string = (hours == 1) ? SINGLE_HOUR : PLURAL_HOUR;
        let minutesUnits: string = (minutes == 1) ? SINGLE_MINUTE : PLURAL_MINUTE;
        display = `${hours} ${hoursUnits}, ${minutes} ${minutesUnits} ${tense}`;
      } else {
        let days: number = Math.floor(value / MILLISECONDS_IN_DAY);
        let daysUnits: string = (days == 1) ? SINGLE_DAY : PLURAL_DAY;
        display = `${days} ${daysUnits} ${tense}`;
      }
    }

    return display;
  }
}

There's a lot happening there. Let me go over it one piece at a time.

For starters, all those constants at the top are just convenient, and intuitive, ways to refer to certain lengths of time. Nothing special there.

Next, take a look at the @Pipe decorator. That tells Angular that this class supports a pipe.

And that "name" option you see inside the parentheses unsurprisingly discloses the name of the pipe that you'll use in the HTML. In this case it's "timeDifference."

Using the @Pipe decorator isn't enough to make this pipe work right, though. That's why the class above implements PipeTransform.

And that interface declares the transform() method so that's the only method you see in the class above.

That transform() method accepts a single parameter: a numerical value that represents the difference between two Date objects.

If the number is negative, then the activity date is in the past. If it's a task that isn't completed, then it's overdue.

But if it's positive, it's still at some point in the future.

How far into the future or past depends on the value itself. It's measured in milliseconds.

The default tense is "from now." As in: "27 minutes from now." That represents a date and time in the future.

But if that number is negative, the tense changes to "ago." As in: "27 minutes ago." That represents a date and time in the past.

You can see the change in the first if() statement above.

If the value is less than 60,000, then the event is either less than a minute in the past or less than a minute in the future. And the display text becomes "less than a minute."

If the value is less than 360,000 but more than 60,000, then the event is either less than an hour in the past or less an hour in the future. The display gets updated accordingly.

On an on.

Eventually, the whole thing returns the display text which will read something like: "5 days, 42 minutes from now" or "37 minutes ago."

Get That in the Module

Next, you need to add that pipe to the module. In my case, I put it in ActivitiesModule

...
@NgModule({
  declarations: [
    ActivityDateDisplayPipe,
    TimeDifferencePipe,
...
  ],
...

That's an easy miss if you're new to Angular.

Full Service

Next, create an Observable that emits a new value every second. I like to put that kind of thing in a service.

Since this countdown is all about dates and times, I'll just go ahead and put mine in DateService.

  private $_counterBySecond: Observable<number>;

  ...

  get counterBySecond(): Observable<number> {
    if (!this.$_counterBySecond) {
      this.$_counterBySecond = interval(1000).pipe(
        map((x) => {
          return Date.now();
        })
      );
    }

    return this.$_counterBySecond;
  }

As promised, that code emits a new value every value every second. That value happens to be the number of milliseconds since 1970, or the current epoch, began.

In case you're unfamiliar with that concept, that's what you'll get every time you invoke Date.now() in TypeScript.

Normally, the RxJS function interval() will just emit numbers in sequence. But here those numbers are getting mapped (via map()) to the current date and time in milliseconds.

So any subscriber will get that date and time value as well.

Cooking With the Component

Next, make some updates to the component to keep track of the time difference. First, add a variable to track the difference:

currentDateDifference: number;

And you'll also need a Subscription object:

counterSubscription: Subscription;

Once the activity is loaded, it's time to get the counter started...

  private initializeCounter() {
    let localStartDate: number = this.dateService.convertToLocal(this.activity.startDate);

    this.counterSubscription = this.dateService.counterBySecond.subscribe(
      timestamp => {
        this.currentDateDifference = localStartDate - timestamp;
      }
    );
  }

First, that method converts the activity's start date to the local date and time. That's necessary because the dates are all stored in UTC and the current user might not live in a UTC+0 time zone.

Then, the method subscribes to the Observable you just created. It will receive a new value every second.

With every emission, the application will calculate the difference between the activity's start date and the current date and time. Remember: that difference is measured in milliseconds.

Once the difference is calculated, it sets the value of currentDateDifference to that number.

Note that the subscription above uses the counterSubscription object. That's so you can unsubscribe to it later and prevent memory leaks.

Here's how to unsubscribe:

  ngOnDestroy(): void {
    if (this.counterSubscription) {
      this.counterSubscription.unsubscribe();
    }
  }

That will unsubscribe when the component is destroyed.

Making It With Markup

Finally, update the HTML like this:

<div [displayActivityDateDifference]="currentDateDifference" [activity]="activity">
    {{currentDateDifference | timeDifference}}
</div>

Ignore the attribute directive and input binding in the top <div> tag. They have nothing to do with this guide.

But take a look at the code between the <div> tags. That's the stuff in the curly braces.

The code there takes the currentDateDifference value from the component (you just saw that above) and prints it out to the screen.

BUT... it applies a pipe to that output to transform it. That pipe is called timeDifference and it's the pipe you saw a few sections above.

So instead of simply displaying the time difference in milliseconds, it displays something like "4 hours, 22 minutes from now."

That's all thanks to the pipe.

Wrapping It Up

Now you know how to create a countdown timer in Angular.

Again: if you want to show a countdown that updates every second, you can do that. Just adapt the code you see in the pipe above to suit your style.

Have fun!

Photo by Jordan Benton from Pexels