Skip to content

Angular API Calls

Beyond a single GET request, real applications need to combine, cache, and cancel API calls correctly. This lesson covers those practical patterns.

Structuring API Calls in a Real App

Real applications rarely make a single, isolated API call. Views often need data from multiple endpoints, need to avoid re-fetching unchanged data, and need to cancel in-flight requests when a component is destroyed before the response arrives.

A clean layering, components call services, services call HttpClient, keeps these concerns manageable as an application grows.

@Injectable({ providedIn: 'root' })
export class OrderService {
  private http = inject(HttpClient);

  getOrderWithItems(orderId: string) {
    return this.http.get<Order>(`/api/orders/${orderId}`).pipe(
      switchMap(order =>
        this.http.get<Item[]>(`/api/orders/${orderId}/items`).pipe(
          map(items => ({ ...order, items }))
        )
      )
    );
  }
}

switchMap chains a second request after the first resolves, combining both results into a single object.

Combining Multiple Requests

forkJoin({ a: http.get(urlA), b: http.get(urlB) })
combineLatest([obsA$, obsB$])
switchMap(value => http.get(url(value)))
  • forkJoin runs multiple requests in parallel and emits once, after all of them complete.
  • combineLatest combines multiple ongoing streams, re-emitting whenever any of them produces a new value.
  • switchMap chains a dependent second request after the first resolves, and cancels a stale in-flight request if the source emits again.
  • Choosing the right combinator avoids manual subscription nesting ("callback pyramids").

API Call Patterns Cheatsheet

Common RxJS operators for combining and managing API requests.

Operator/Pattern Use Case
forkJoin Run independent requests in parallel, wait for all
switchMap Chain a dependent request, cancel stale ones automatically
combineLatest Combine multiple ongoing streams reactively
takeUntilDestroyed() Automatically unsubscribe when the component is destroyed
shareReplay(1) Cache and share the latest emitted value among subscribers
catchError Handle request failures gracefully

Parallel Requests With forkJoin

When a view needs data from multiple independent endpoints at once, forkJoin runs them in parallel and emits a single combined result once every request has completed, rather than waiting for them sequentially.

loadDashboard() {
  return forkJoin({
    profile: this.http.get<Profile>('/api/profile'),
    stats: this.http.get<Stats>('/api/stats'),
  });
}

Canceling In-Flight Requests

If a component is destroyed before its API call resolves (the user navigates away quickly), the subscription should be cleaned up to avoid updating a component that no longer exists. takeUntilDestroyed() automates this cleanup.

export class SearchComponent {
  private http = inject(HttpClient);
  private destroyRef = inject(DestroyRef);

  search(term: string) {
    this.http
      .get<Result[]>(`/api/search?q=${term}`)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(results => (this.results = results));
  }
}

Caching Responses With shareReplay

For data that doesn't change often (like a list of countries or app configuration), shareReplay(1) caches the latest emitted value and shares it with every subscriber, avoiding a repeated network request for data you already have.

@Injectable({ providedIn: 'root' })
export class ConfigService {
  private http = inject(HttpClient);
  private config$ = this.http.get<AppConfig>('/api/config').pipe(shareReplay(1));

  getConfig() {
    return this.config$;
  }
}

Common Mistakes

  • Nesting .subscribe() calls inside other .subscribe() calls instead of using switchMap/forkJoin to compose requests.
  • Not cleaning up subscriptions for slow requests when a component is destroyed before the response arrives.
  • Re-fetching the same rarely-changing data on every component instantiation instead of caching with shareReplay.
  • Using forkJoin when requests genuinely depend on each other's results, which needs switchMap chaining instead.

Key Takeaways

  • forkJoin combines independent parallel requests; switchMap chains dependent sequential requests.
  • takeUntilDestroyed() cleans up in-flight request subscriptions automatically when a component is destroyed.
  • shareReplay(1) caches and shares a response across multiple subscribers, avoiding redundant network calls.
  • Keep API composition logic inside services, not scattered across component subscribe callbacks.

Pro Tip

Use takeUntilDestroyed() (from @angular/core/rxjs-interop) by default on any subscription initiated from a component; it eliminates an entire category of memory leak and "updating a destroyed component" bugs with a single operator.