Skip to content

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:

  1. Shares a Single Subscription: It subscribes to the source Observable only when the first subscriber arrives.
  2. Multicasts Live Values: It pushes values from the source to all currently active subscribers.
  3. 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.
  4. 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. Use shareReplay({ bufferSize: 1, refCount: true }) instead. Using share 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 or shareReplay({ 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:

  1. SharedTimerService creates an interval(2000) Observable and applies share() to it, storing the result in sharedTicks$.
  2. TickDisplayAComponent subscribes immediately in ngOnInit. This is the first subscription. share subscribes to the source interval, which starts emitting 0, 1, 2... every 2 seconds. Component A receives all these ticks.
  3. TickDisplayBComponent waits 5 seconds before subscribing in its ngOnInit.
  4. When Component B subscribes, the source interval (shared via share) is already running and might have already emitted ticks 0 and 1.
  5. 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).
  6. Both components receive subsequent ticks (3, 4, 5...) simultaneously as they are emitted by the single, shared interval.
  7. If both components are destroyed, their subscriptions (managed by takeUntilDestroyed) end. share sees the subscriber count is zero and unsubscribes from the source interval, 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.