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:
-
timer(dueTime):- Waits for the specified
dueTime(in milliseconds, or aDate). - Emits a single value:
0. - Immediately completes.
- Waits for the specified
-
timer(initialDelay, period):- Waits for the specified
initialDelay(in milliseconds). - Emits the first value:
0. - Then, waits for
periodmilliseconds. - Emits the next value:
1. - Continues emitting sequential numbers (
2,3, ...) everyperiodmilliseconds. - This form never completes on its own (just like
interval).
- Waits for the specified
At a glance
- Signature:
timer(dueTime)for one delayed emission,timer(initialDelay, period)for recurring emissions - Use when: delayed one-shot actions, or polling with a custom start delay (including
timer(0, n)for start-now polling) - Avoid when: a plain fixed cadence is all you need and
intervalreads clearer - Top gotcha: the single-argument form completes after one value; the two-argument form never completes and needs teardown
Key Characteristics#
- Asynchronous: Emissions happen after specified delays.
- Completion:
- Completes after one emission (
0) if onlydueTimeis provided. - Never completes if
periodis 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, emits0, waits 1000ms, emits1, ...timer(1000): Waits 1000ms, emits0, completes.timer(0, 1000): Waits 0ms (emits0immediately), waits 1000ms, emits1, waits 1000ms, emits2, ... (Starts immediately, then intervals).timer(5000, 1000): Waits 5000ms, emits0, waits 1000ms, emits1, waits 1000ms, emits2, ... (Initial delay before starting intervals).
Minimal Example#
import { timer } from "rxjs";
timer(2000).subscribe({
next: console.log,
complete: () => console.log("complete"),
});
// after 2s: 0, complete
timer(0, 1000).subscribe(console.log);
// immediately: 0, then 1, 2, 3... every second (never completes)
Real-World Example Scenarios#
-
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.
-
timer(initialDelay, period)Scenario: Delayed Polling- Similar to the
intervalpolling 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.
- Similar to the
Code Snippet 1 (Using timer(dueTime) - Delayed Message):
import { Component, signal } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { timer } from "rxjs";
@Component({
selector: "app-delayed-message",
template: `
<h4>Welcome!</h4>
@if (showHelpMessage()) {
<div class="tooltip-message">
Looks like you've been here a few seconds. Need any help?
</div>
}
`,
})
export class DelayedMessageComponent {
protected readonly showHelpMessage = signal(false);
constructor() {
// emits 0 once after 3s, then completes;
// takeUntilDestroyed covers early navigation away
timer(3000)
.pipe(takeUntilDestroyed())
.subscribe(() => this.showHelpMessage.set(true));
}
}
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, inject, signal } from "@angular/core";
import { DatePipe, JsonPipe } from "@angular/common";
import { HttpClient } from "@angular/common/http";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { EMPTY, catchError, switchMap, timer } from "rxjs";
@Component({
selector: "app-delayed-poller",
imports: [DatePipe, JsonPipe],
template: `
<h4>Delayed Server Status Check</h4>
<p>Waiting 5s initially, then checking every 10s...</p>
@if (status(); as current) {
<div>
<strong>Last Status:</strong> {{ current | json }}
<br />
<em>Last Checked: {{ lastChecked() | date: "mediumTime" }}</em>
</div>
}
@if (errorMessage()) {
<div><strong>Error:</strong> {{ errorMessage() }}</div>
}
`,
})
export class DelayedPollerComponent {
private readonly http = inject(HttpClient);
private readonly INITIAL_DELAY_MS = 5000; // 5 seconds
private readonly POLLING_PERIOD_MS = 10000; // 10 seconds
protected readonly status = signal<unknown | null>(null);
protected readonly lastChecked = signal<Date | null>(null);
protected readonly errorMessage = signal("");
constructor() {
// waits 5s, emits 0, then emits 1, 2,... every 10s
timer(this.INITIAL_DELAY_MS, this.POLLING_PERIOD_MS)
.pipe(
switchMap(() =>
this.http.get<unknown>("/api/server/status").pipe(
catchError((error) => {
this.errorMessage.set(
`Failed to fetch status (${error.statusText || "Unknown Error"})`,
);
this.status.set(null);
return EMPTY; // skip this cycle, keep polling
}),
),
),
takeUntilDestroyed(), // required: this timer variant never completes
)
.subscribe((statusData) => {
this.status.set(statusData);
this.lastChecked.set(new Date());
this.errorMessage.set("");
});
}
}
Explanation:
- Example 1:
timer(3000)waits 3 seconds, emits0, completes. Useful for one-off delayed actions. - Example 2:
timer(5000, 10000)waits 5 seconds, emits0, then continues emitting1, 2, ...at 10-second intervals. Because this variant never completes,takeUntilDestroyed()ties the polling loop to the component's lifetime.
Common Mistakes#
Forgetting which form completes. timer(n) is one-shot and self-completing; timer(n, p) is infinite. Mixing them up either kills a polling loop after one round or leaks a timer forever.
Reimplementing timer(0, n) with extra operators. interval(n).pipe(startWith(-1)) and friends work, but timer(0, n) says "start now, repeat every n" in one call.
Using timer for per-emission delays. Shifting an existing stream's values is delay's job; timer creates a new source.
Interview Q&A#
What are the differences between timer and interval?
interval(n) always waits one period before the first value and repeats forever. timer decouples the first emission from the cadence: timer(0, n) starts immediately, timer(d) fires once after d and completes. Everything interval does, timer can express.
How would you implement a one-time delayed action that is safe if the component dies first?
timer(delay).pipe(takeUntilDestroyed()).subscribe(...). The timer self-completes after firing, and the interop operator guards the window before it fires, both paths end the subscription cleanly.
Can timer take a Date?
Yes: timer(new Date(targetTime)) emits when the wall clock reaches the date, useful for scheduled UI events like session-expiry warnings.
Related#
- interval for plain fixed-cadence ticking
- delay to shift an existing stream instead of creating one
- debounceTime, which uses the same timer concept reactively
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!