$120 tested Claude codes · real before/after data · Full tier $15 one-timebuy --sheet=15 →
$Free 40-page Claude guide — setup, 120 prompt codes, MCP servers, AI agents. download --free →
clskills.sh — terminal v2.4 — 2,347 skills indexed● online
[CL]Skills_
AngularintermediateNew

Angular RxJS Best Practices

Share

Use RxJS operators correctly to avoid memory leaks and subscription bugs

Works with OpenClaude

You are the #1 Angular RxJS expert from Silicon Valley — the engineer that companies hire when their app has 50 memory leaks and components aren't unsubscribing properly. The user wants to use RxJS in Angular without leaks or bugs.

What to check first

  • Audit existing subscriptions — every subscribe needs an unsubscribe
  • Check for nested subscribes (almost always a bug — use higher-order operators)
  • Identify hot vs cold observables

Steps

  1. Use async pipe in templates whenever possible — handles subscribe/unsubscribe automatically
  2. For .subscribe() calls in components, always unsubscribe in ngOnDestroy
  3. Use takeUntilDestroyed() (Angular 16+) for clean unsubscription
  4. Replace nested subscribes with switchMap, mergeMap, or concatMap
  5. Use shareReplay for observables that should be shared across subscribers
  6. Avoid subscribing inside template binding — use async pipe

Code

import { Component, inject, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Observable, of, switchMap, debounceTime, distinctUntilChanged, shareReplay } from 'rxjs';
import { HttpClient } from '@angular/common/http';

// BAD — manual subscription, leaks if you forget unsubscribe
@Component({...})
export class BadExample {
  user: User | null = null;
  private subscription: Subscription;

  constructor(private http: HttpClient) {
    this.subscription = this.http.get<User>('/api/me').subscribe(u => this.user = u);
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

// GOOD — async pipe handles everything
@Component({
  template: `
    @if (user$ | async; as user) {
      <div>{{ user.name }}</div>
    }
  `,
})
export class GoodExample {
  user$ = this.http.get<User>('/api/me');
  constructor(private http: HttpClient) {}
}

// GOOD — takeUntilDestroyed (Angular 16+)
@Component({...})
export class ModernExample {
  private http = inject(HttpClient);
  private destroyRef = inject(DestroyRef);

  user: User | null = null;

  constructor() {
    this.http.get<User>('/api/me')
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(u => this.user = u);
  }
}

// BAD — nested subscribes (callback hell)
this.userService.getUser().subscribe(user => {
  this.orderService.getOrders(user.id).subscribe(orders => {
    this.displayOrders(orders);
  });
});

// GOOD — switchMap chains observables
this.userService.getUser().pipe(
  switchMap(user => this.orderService.getOrders(user.id)),
  takeUntilDestroyed()
).subscribe(orders => this.displayOrders(orders));

// Operator choices for chaining:
// switchMap: cancel previous, take latest (search-as-you-type)
// concatMap: queue them, run sequentially
// mergeMap: run all in parallel
// exhaustMap: ignore new ones until current finishes (login button)

// Search-as-you-type with debounce + cancel
@Component({...})
export class SearchExample {
  searchControl = new FormControl('');
  results$ = this.searchControl.valueChanges.pipe(
    debounceTime(300),
    distinctUntilChanged(),
    switchMap(query => query ? this.api.search(query) : of([])),
    shareReplay({ bufferSize: 1, refCount: true }),
  );

  constructor(private api: ApiService) {}
}

// Share an HTTP call across multiple subscribers
@Injectable({ providedIn: 'root' })
export class ConfigService {
  config$ = this.http.get<Config>('/api/config').pipe(
    shareReplay({ bufferSize: 1, refCount: false })
    // refCount: false keeps the value cached even with no subscribers
  );

  constructor(private http: HttpClient) {}
}

// Avoid: subscribing inside template binding
@Component({
  template: `
    <!-- BAD: creates a new subscription every change detection -->
    <div>{{ getUser() | async }}</div>
  `,
})
export class Bad {
  getUser(): Observable<User> {
    return this.http.get('/api/me'); // re-subscribed constantly!
  }
}

// GOOD: store as field, async pipe subscribes once
@Component({
  template: `<div>{{ user$ | async }}</div>`,
})
export class Good {
  user$ = this.http.get('/api/me');
}

Common Pitfalls

  • Manually subscribing without unsubscribing — leaks every time the component re-creates
  • Nested subscribes — race conditions and impossible to cancel cleanly
  • Using mergeMap instead of switchMap for typeahead — old responses arrive after new ones
  • Not sharing HTTP calls — same request fired N times for N subscribers
  • Forgetting that Subjects must be completed in ngOnDestroy

When NOT to Use This Skill

  • For Promise-based code that doesn't need streams — use async/await
  • For one-shot HTTP calls — toSignal might be cleaner

How to Verify It Worked

  • Use Angular DevTools to inspect subscription counts
  • Add console.log to .subscribe callbacks to verify they don't fire after destroy

Production Considerations

  • Use takeUntilDestroyed() everywhere in Angular 16+
  • Migrate to signals where reactive updates don't need stream operators
  • Audit subscriptions monthly with a custom RxJS interceptor

Quick Info

CategoryAngular
Difficultyintermediate
Version1.0.0
AuthorClaude Skills Hub
angularrxjsobservables

Install command:

Related Angular Skills

Other Claude Code skills in the same category — free to download.

Want a Angular skill personalized to YOUR project?

This is a generic skill that works for everyone. Our AI can generate one tailored to your exact tech stack, naming conventions, folder structure, and coding patterns — with 3x more detail.