interval
interval()
is an RxJS creation operator that generates an Observable which emits sequential numbers (0, 1, 2, 3, and so on) at a specified, regular time interval (in milliseconds).
Think of it as setting up a metronome that ticks indefinitely, emitting the tick count each time.
Key Characteristics#
- Sequential Numbers: Emits
0
, then1
, then2
, ... - Timed Emissions: You specify the delay between emissions (e.g.,
interval(1000)
emits every 1 second). - Asynchronous: The emissions happen asynchronously based on the timer you set.
- Never Completes: This is important! By default,
interval()
never stops emitting on its own. It will run forever unless you explicitly unsubscribe or use another operator (liketake
) to limit it. - Cold Observable: Each subscription starts its own independent timer. If two parts of your code subscribe to
interval(1000)
, they will each get their own sequence starting from 0.
Real-World Example Scenario#
A very common use case in web applications, including Angular, is polling. Imagine you need to check a server endpoint repeatedly to see if there's new data available, like:
- Checking for new chat messages every 5 seconds.
- Updating a dashboard with fresh statistics every 30 seconds.
- Checking the status of a long-running background job every 10 seconds.
interval()
provides the timed trigger for making these checks.
Code Snippet (Angular Component - Polling Example)#
Let's create a component that checks for hypothetical server status updates every 5 seconds (5000 milliseconds). We'll use interval()
to trigger the check and HttpClient
to make the request. We must remember to clean up the interval when the component is destroyed.
import { Component, OnInit, OnDestroy } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { interval, Subscription, Observable } from "rxjs";
import { switchMap, startWith, catchError, tap } from "rxjs/operators";
@Component({
selector: "app-status-poller",
template: `
<h4>Server Status Check</h4>
<p>Checking every 5 seconds...</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 StatusPollerComponent implements OnInit, OnDestroy {
status: any = null;
lastChecked: Date | null = null;
errorMessage: string = "";
// To hold the subscription to the interval observable
private pollingSubscription: Subscription | undefined;
private readonly POLLING_INTERVAL_MS = 5000; // 5 seconds
constructor(private http: HttpClient) {}
ngOnInit(): void {
console.log(
`Starting status polling now (${new Date().toLocaleString()})...`
);
// Create an observable that emits every 5 seconds
const pollingInterval$ = interval(this.POLLING_INTERVAL_MS);
this.pollingSubscription = pollingInterval$
.pipe(
// Use startWith(0) to trigger the first check immediately,
// instead of waiting for the first 5-second interval to pass.
startWith(0), // Emit 0 immediately, then continue with interval 0, 1, 2...
tap(() =>
console.log("Polling interval triggered... Fetching status.")
),
// For each emission from the interval, switch to making an HTTP GET request
switchMap(() => {
// switchMap cancels previous pending HTTP requests if the interval fires again quickly
return this.http.get<any>("/api/server/status").pipe(
// Replace with your actual API endpoint
catchError((error) => {
// Handle HTTP errors gracefully
console.error("Error fetching status:", error);
this.errorMessage = `Failed to fetch status (${
error.statusText || "Unknown Error"
})`;
this.status = null; // Clear previous status on error
// Return an empty observable or throwError to stop polling if needed
// For this example, we'll let polling continue
return []; // Don't emit anything on error, effectively skipping this interval's update
})
);
})
)
.subscribe({
next: (statusData) => {
// Successfully got status data from the API
console.log("Status received:", statusData);
this.status = statusData;
this.lastChecked = new Date(); // Record the time of the successful check
this.errorMessage = ""; // Clear any previous error message
},
error: (err) => {
// This would typically catch errors NOT handled by the inner catchError,
// or if catchError re-throws an error.
console.error("Polling stream error:", err);
this.errorMessage = "Polling mechanism failed.";
},
// Note: No 'complete' handler needed as interval() doesn't complete.
});
}
ngOnDestroy(): void {
// VERY IMPORTANT: Unsubscribe when the component is destroyed!
// Otherwise, the interval and polling will continue running in the background indefinitely,
// causing memory leaks and unnecessary network requests.
if (this.pollingSubscription) {
this.pollingSubscription.unsubscribe();
console.log("Polling stopped and unsubscribed.");
}
}
}
Explanation:
interval(5000)
: Creates the basic timer emitting0, 1, 2,...
every 5 seconds.startWith(0)
: We often want to check immediately when the component loads, not wait for the first interval.startWith(0)
makes it emit0
right away.switchMap(...)
: This is key for polling. When the interval emits,switchMap
subscribes to the inner Observable (thehttp.get()
call). If the interval emits again before the HTTP request finishes,switchMap
automatically cancels the previous pending HTTP request and starts a new one. This prevents outdated responses or unnecessary concurrent requests for simple polling.catchError(...)
: InsideswitchMap
, we catch errors from thehttp.get()
call specifically, allowing us to handle API errors without stopping the entire pollinginterval
.subscribe(...)
: We process the successful status updates received from the API.ngOnDestroy()
/unsubscribe()
: This is critical. We store the subscription returned by.subscribe()
and call.unsubscribe()
on it when the component is destroyed. This stops theinterval
timer and prevents memory leaks.
Summary#
interval()
is a fundamental tool for creating streams based on timed intervals, frequently used for polling or triggering periodic actions, but always requiring careful handling of unsubscription to avoid issues.