Skip to content

timer

timer() is an RxJS creation operator that creates an Observable which emits values after a specified delay, and can optionally continue emitting values at regular intervals thereafter.

It behaves differently based on the arguments you provide:

  1. timer(dueTime):

    • Waits for the specified dueTime (in milliseconds, or a Date).
    • Emits a single value: 0.
    • Immediately completes.
  2. timer(initialDelay, period):

    • Waits for the specified initialDelay (in milliseconds).
    • Emits the first value: 0.
    • Then, waits for period milliseconds.
    • Emits the next value: 1.
    • Continues emitting sequential numbers (2, 3, ...) every period milliseconds.
    • This form never completes on its own (just like interval).

Key Characteristics#

  • Asynchronous: Emissions happen after specified delays.
  • Completion:
  • Completes after one emission (0) if only dueTime is provided.
  • Never completes if period is also provided.
  • Initial Delay: The second form allows a specific delay before the first emission, which is different from interval (where the first emission occurs after the first period).
  • Cold Observable: Each subscription starts its own independent timer.

Difference from interval()#

  • interval(1000): Waits 1000ms, emits 0, waits 1000ms, emits 1, ...
  • timer(1000): Waits 1000ms, emits 0, completes.
  • timer(0, 1000): Waits 0ms (emits 0 immediately), waits 1000ms, emits 1, waits 1000ms, emits 2, ... (Starts immediately, then intervals).
  • timer(5000, 1000): Waits 5000ms, emits 0, waits 1000ms, emits 1, waits 1000ms, emits 2, ... (Initial delay before starting intervals).

Real-World Example Scenarios#

  1. timer(dueTime) Scenario: Delayed Action / Welcome Message

    • Imagine you want to show a "Need help?" tooltip or a welcome message in your Angular app, but only after the user has been on the page for, say, 3 seconds, giving them time to look around first. You only want this message to appear once.
  2. timer(initialDelay, period) Scenario: Delayed Polling

    • Similar to the interval polling example, but maybe you don't want to start checking the server status immediately on component load. Perhaps you want to wait 5 seconds for initial setup/rendering to finish, and then start checking every 10 seconds.

Code Snippet 1 (Using timer(dueTime) - Delayed Message):

import { Component, OnInit } from "@angular/core";
import { timer, Subscription } from "rxjs";

@Component({
  selector: "app-delayed-message",
  template: `
    <h4>Welcome!</h4>
    <div *ngIf="showHelpMessage" class="tooltip-message">
      Looks like you've been here a few seconds. Need any help?
    </div>
  `,
})
export class DelayedMessageComponent implements OnInit {
  showHelpMessage = false;
  private timerSubscription: Subscription | undefined;

  ngOnInit(): void {
    const messageDelay = 3000; // 3 seconds
    console.log(
      `Component initialized at ${new Date().toLocaleTimeString()}. Setting timer for ${messageDelay}ms.`
    );

    // Create an observable that emits 0 after 3 seconds, then completes.
    const delayTimer$ = timer(messageDelay);

    this.timerSubscription = delayTimer$.subscribe({
      next: (value) => {
        // This will be called once with value 0 after 3 seconds
        console.log(
          `Timer emitted ${value} at ${new Date().toLocaleTimeString()}. Showing message.`
        );
        this.showHelpMessage = true;
      },
      complete: () => {
        // This will be called immediately after the 'next' emission
        console.log("Delay timer completed.");
        // Since it completes, explicit unsubscription in ngOnDestroy for *this specific timer*
        // isn't strictly necessary for leak prevention, but is still good practice
        // if the component could be destroyed *before* the timer fires.
      },
    });
  }

  // Good practice to include, especially if combining with other subscriptions
  ngOnDestroy(): void {
    if (this.timerSubscription) {
      this.timerSubscription.unsubscribe();
      console.log(
        "Delayed message timer unsubscribed (if it was still running)."
      );
    }
  }
}

Code Snippet 2 (Using timer(initialDelay, period) - Delayed Polling):

Let's adapt the polling example to wait 5 seconds initially, then poll every 10 seconds.

import { Component, OnInit, OnDestroy } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { timer, Subscription, Observable } from "rxjs";
import { switchMap, catchError, tap } from "rxjs/operators";

@Component({
  selector: "app-delayed-poller",
  template: `
    <h4>Delayed Server Status Check</h4>
    <p>Waiting 5s initially, then checking every 10s...</p>
    <div *ngIf="status">
      <strong>Last Status:</strong> {{ status | json }}
      <br />
      <em>Last Checked: {{ lastChecked | date : "mediumTime" }}</em>
    </div>
    <div *ngIf="errorMessage"><strong>Error:</strong> {{ errorMessage }}</div>
  `,
})
export class DelayedPollerComponent implements OnInit, OnDestroy {
  status: any = null;
  lastChecked: Date | null = null;
  errorMessage: string = "";

  private pollingSubscription: Subscription | undefined;
  private readonly INITIAL_DELAY_MS = 5000; // 5 seconds
  private readonly POLLING_PERIOD_MS = 10000; // 10 seconds

  constructor(private http: HttpClient) {}

  ngOnInit(): void {
    console.log(
      `Starting delayed polling now (${new Date().toLocaleString()}). Initial delay: ${
        this.INITIAL_DELAY_MS
      }ms, Period: ${this.POLLING_PERIOD_MS}ms.`
    );

    // Create timer: waits 5s, emits 0, then emits 1, 2,... every 10s
    const pollingTimer$ = timer(this.INITIAL_DELAY_MS, this.POLLING_PERIOD_MS);

    this.pollingSubscription = pollingTimer$
      .pipe(
        tap((count) =>
          console.log(`Polling timer emitted ${count}. Fetching status.`)
        ),
        // Switch to HTTP request on each timer emission
        switchMap((count) => {
          return this.http.get<any>("/api/server/status").pipe(
            // Use your actual endpoint
            catchError((error) => {
              console.error(
                `Error fetching status (emission ${count}):`,
                error
              );
              this.errorMessage = `Failed to fetch status (${
                error.statusText || "Unknown Error"
              })`;
              this.status = null;
              return []; // Continue polling even after error
            })
          );
        })
      )
      .subscribe({
        next: (statusData) => {
          console.log("Status received:", statusData);
          this.status = statusData;
          this.lastChecked = new Date();
          this.errorMessage = "";
        },
        error: (err) => {
          console.error("Polling stream error:", err);
          this.errorMessage = "Polling mechanism failed.";
        },
        // No 'complete' handler here because this timer variant never completes
      });
  }

  ngOnDestroy(): void {
    // VERY IMPORTANT for the timer(initialDelay, period) variant!
    if (this.pollingSubscription) {
      this.pollingSubscription.unsubscribe();
      console.log("Delayed polling stopped and unsubscribed.");
    }
  }
}

Explanation:

  • Example 1: timer(3000) waits 3 seconds, emits 0, completes. Useful for one-off delayed actions.
  • Example 2: timer(5000, 10000) waits 5 seconds, emits 0, then continues emitting 1, 2, ... at 10-second intervals. This requires careful unsubscription in ngOnDestroy just like interval.

Summary#

timer() provides more flexibility than interval() for controlling when emissions start, offering both a one-shot delay (timer(dueTime)) and a recurring emission with an initial offset (timer(initialDelay, period)). Remember to unsubscribe from the recurring variant!