typescript 44 lines · 8 steps

How a signal-driven countdown works in Angular

An Angular standalone component ticks down a signal every second and emits when it hits zero.

Explained by highlit
1import { Component, computed, DestroyRef, inject, input, output, signal } from '@angular/core';
2import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3import { interval, map, takeWhile } from 'rxjs';
4 
5@Component({
6 selector: 'app-countdown',
7 standalone: true,
8 template: `
9 <span class="countdown" [class.expiring]="remaining() <= 10">
10 {{ formatted() }}
11 </span>
12 `,
13})
14export class CountdownComponent {
15 readonly seconds = input.required<number>();
16 readonly expired = output<void>();
17 
18 private readonly destroyRef = inject(DestroyRef);
19 private readonly remaining = signal(0);
20 
21 protected readonly formatted = computed(() => {
22 const total = this.remaining();
23 const mins = Math.floor(total / 60);
24 const secs = total % 60;
25 return `${mins}:${secs.toString().padStart(2, '0')}`;
26 });
27 
28 ngOnInit(): void {
29 this.remaining.set(this.seconds());
30 
31 interval(1000)
32 .pipe(
33 map(() => this.remaining() - 1),
34 takeWhile((value) => value >= 0),
35 takeUntilDestroyed(this.destroyRef),
36 )
37 .subscribe((value) => {
38 this.remaining.set(value);
39 if (value === 0) {
40 this.expired.emit();
41 }
42 });
43 }
44}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Signals let a component's template react automatically to state changes without manual change detection.
  2. 2computed derives display values from source signals, recalculating only when its dependencies change.
  3. 3takeUntilDestroyed ties an RxJS subscription's lifetime to the component, preventing timer leaks.

Related explainers

typescript
type TokenType = "keyword" | "string" | "comment" | "number" | "text";
 
interface Token {
  type: TokenType;

How a regex tokenizer highlights code

tokenizer regex lexing
Intermediate 10 steps
typescript
import { Component, inject, signal } from '@angular/core';
import { CdkDragDrop, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
import { HttpClient } from '@angular/common/http';
import { finalize } from 'rxjs';

Drag-and-drop reordering with signals in Angular

drag-and-drop signals optimistic-update
Intermediate 8 steps
typescript
type Middleware<TIn, TOut> = (ctx: TIn) => Promise<TOut> | TOut;
 
class Pipeline<TIn, TOut> {
  private constructor(private readonly run: Middleware<TIn, TOut>) {}

A type-safe async middleware pipeline

generics type-safety middleware
Advanced 9 steps
typescript
type Countdown = {
  days: number;
  hours: number;
  minutes: number;

Building a self-stopping countdown timer

date-math closures timers
Intermediate 9 steps
python
from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver

Busting template fragment caches in Django

caching signals cache-invalidation
Intermediate 4 steps
typescript
export function isValidCardNumber(input: string): boolean {
  const digits = input.replace(/[\s-]/g, "");
 
  if (!/^\d{12,19}$/.test(digits)) {

Validating card numbers with the Luhn check

luhn-algorithm checksum input-validation
Intermediate 7 steps

Share this explainer

Here's the card — post it anywhere.

How a signal-driven countdown works in Angular — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code