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
period
milliseconds. - Emits the next value:
1
. - Continues emitting sequential numbers (
2
,3
, ...) everyperiod
milliseconds. - This form never completes on its own (just like
interval
).
- Waits for the specified
Key Characteristics#
- Asynchronous: Emissions happen after specified delays.
- Completion:
- Completes after one emission (
0
) if onlydueTime
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, emits0
, waits 1000ms, emits1
, ...timer(1000)
: Waits 1000ms, emits0
, completes.timer(0, 1000)
: Waits 0ms (emits0
immediately), 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).
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
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.
- Similar to the
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, 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. This requires careful unsubscription inngOnDestroy
just likeinterval
.
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!