share
Like shareReplay
, the share
operator is used to share a single subscription to an underlying source Observable among multiple downstream subscribers. This prevents the source Observable's logic (e.g., setting up an interval, making a connection) from executing multiple times.
However, share
behaves like it's using a plain Subject
internally for multicasting. This means:
- Shares a Single Subscription: It subscribes to the source Observable only when the first subscriber arrives.
- Multicasts Live Values: It pushes values from the source to all currently active subscribers.
- No Replay: If a subscriber joins after the source has already emitted some values, that new subscriber will not receive those past values. They will only get emissions that happen after they subscribed.
- Reference Counting: It uses reference counting (
refCount
is implicitly true). The subscription to the source is active only as long as there's at least one downstream subscriber. When the last subscriber leaves,share
unsubscribes from the source. If a new subscriber arrives later, it will re-subscribe to the source, potentially restarting it.
Analogy#
Think of a live conference call or radio talk show without any recording.
- The Show/Call (Source Observable): The conversation happening in real-time.
- The Broadcast System (
share
): Connects to the live show once when the first listener joins. - Listeners (Subscribers): People joining the call/tuning in.
- Everyone currently listening hears the same thing at the same time.
- If you join late, you missed the beginning. You only hear the conversation from the moment you joined onwards. There's no way to hear what was said before you connected.
- If everyone hangs up/tunes out, the broadcast system disconnects from the show. If someone calls in again later, it reconnects, and the show might start fresh (depending on the source).
Why Use share
(and when not to)?#
Use Cases:#
- Sharing Observables where past values are irrelevant or shouldn't be replayed (e.g., live event streams, certain WebSocket scenarios where only future messages matter).
- Sharing "hot" Observables or Observables with side effects that should only occur once while there are active listeners (e.g., setting up an interval-based check that runs only when needed).
When NOT to Use#
- HTTP Requests (usually): For typical
HttpClient
GET requests, you almost always want the result cached and replayed. UseshareReplay({ bufferSize: 1, refCount: true })
instead. Usingshare
would mean that if a second component subscribes slightly after the first, and the HTTP request has already completed, the second component might get nothing (if the source completes quickly). - State Management: You typically want the current state value replayed, making
BehaviorSubject
orshareReplay({ bufferSize: 1, ... })
more suitable.
Real-World Example: Shared Interval Timer for Periodic UI Updates#
Imagine you want a timer that ticks every few seconds, and multiple components need to react to these ticks (e.g., to refresh some status indicator). You only want one actual interval
timer running in the background, shared among them. New components subscribing should just sync up with the next tick, not get past ticks.
Code Snippets:
1. Shared Timer Service (timer.service.ts
)
import { Injectable } from "@angular/core";
import { Observable, interval, share, tap, map } from "rxjs";
@Injectable({
providedIn: "root",
})
export class SharedTimerService {
// The shared timer observable
readonly sharedTicks$: Observable<number>;
constructor() {
// Create ONE interval timer
this.sharedTicks$ = interval(2000).pipe(
// Emit every 2 seconds
tap((tick) =>
console.log(
`%c --- Source Interval Emitted: ${tick} --- `,
"background: #eee; color: #999"
)
),
// --- Key Operator ---
// Share this single interval subscription among all subscribers.
// No replay for late subscribers.
share()
// --------------------
// Note: share() is roughly equivalent to:
// multicast(() => new Subject()), // Use a plain Subject (no replay)
// refCount() // Start/stop based on subscriber count
);
}
}
2. Component Displaying Ticks
import {
Component,
inject,
signal,
OnInit,
OnDestroy,
DestroyRef,
} from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { SharedTimerService } from "./timer.service"; // Adjust path
@Component({
selector: "app-tick-display-a",
standalone: true,
template: `
<div class="display-box">
<h4>Tick Display A</h4>
<p>Started Subscribing Immediately</p>
<p>Last Tick Received: {{ lastTick() }}</p>
</div>
`,
styles: [
".display-box { border: 1px solid purple; padding: 10px; margin: 10px; }",
],
})
export class TickDisplayAComponent implements OnInit {
private timerService = inject(SharedTimerService);
private destroyRef = inject(DestroyRef);
lastTick = signal<number | string>("Waiting...");
ngOnInit(): void {
console.log("TickDisplayA: Subscribing to sharedTicks$");
this.timerService.sharedTicks$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((tickValue) => {
console.log(`TickDisplayA: Received tick ${tickValue}`);
this.lastTick.set(tickValue);
});
}
}
3. Another Component Displaying Ticks - Subscribes Late
import {
Component,
inject,
signal,
OnInit,
OnDestroy,
DestroyRef,
} from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { SharedTimerService } from "./timer.service"; // Adjust path
@Component({
selector: "app-tick-display-b",
standalone: true,
template: `
<div class="display-box" style="border-color: teal;">
<h4>Tick Display B</h4>
<p>Started Subscribing After 5 Seconds</p>
<p>Last Tick Received: {{ lastTick() }}</p>
</div>
`,
styles: [
".display-box { border: 1px solid purple; padding: 10px; margin: 10px; }",
],
})
export class TickDisplayBComponent implements OnInit {
private timerService = inject(SharedTimerService);
private destroyRef = inject(DestroyRef);
lastTick = signal<number | string>("Waiting...");
ngOnInit(): void {
// Simulate this component loading or deciding to subscribe later
setTimeout(() => {
console.log("TickDisplayB: Subscribing to sharedTicks$ (after delay)");
this.timerService.sharedTicks$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((tickValue) => {
// *** Key Point ***
// This will likely NOT receive tick 0 or 1, because it subscribed late.
// It will receive the next tick emitted by the *already running* interval.
console.log(`TickDisplayB: Received tick ${tickValue}`);
this.lastTick.set(tickValue);
});
}, 5000); // Subscribe after 5 seconds
}
}
4. App Component
import { Component } from "@angular/core";
import { TickDisplayAComponent } from "./tick-display-a.component"; // Adjust path
import { TickDisplayBComponent } from "./tick-display-b.component"; // Adjust path
@Component({
selector: "app-root",
standalone: true,
imports: [TickDisplayAComponent, TickDisplayBComponent], // Import components
template: `
<h1>RxJS share Demo</h1>
<app-tick-display-a></app-tick-display-a>
<app-tick-display-b></app-tick-display-b>
`,
})
export class AppComponent {}
Explanation:
SharedTimerService
creates aninterval(2000)
Observable and appliesshare()
to it, storing the result insharedTicks$
.TickDisplayAComponent
subscribes immediately inngOnInit
. This is the first subscription.share
subscribes to the sourceinterval
, which starts emitting 0, 1, 2... every 2 seconds. Component A receives all these ticks.TickDisplayBComponent
waits 5 seconds before subscribing in itsngOnInit
.- When Component B subscribes, the source
interval
(shared viashare
) is already running and might have already emitted ticks 0 and 1. - Component B will not receive ticks 0 and 1. Its subscription will start receiving ticks from the next emission of the shared interval (likely tick 2 or 3, depending on timing).
- Both components receive subsequent ticks (3, 4, 5...) simultaneously as they are emitted by the single, shared interval.
- If both components are destroyed, their subscriptions (managed by
takeUntilDestroyed
) end.share
sees the subscriber count is zero and unsubscribes from the sourceinterval
, stopping it.
This demonstrates how share
provides a way to execute a source Observable once and multicast its live values, without the buffering and replay behaviour of shareReplay
.