Skip to the solution
HardFrontend10 min read

Signals signal()/computed()/effect() vs RxJS — when do you use which in Angular 18+?

1k views

The problem

Question presented to candidate: "We are on Angular 22 and half the team wants to replace RxJS with signals everywhere, the other half wants to keep everything in Observables. Where would you draw the line between signal()/computed()/effect() and RxJS, and how do the two talk to each other?"

What a strong answer should cover:

  • Signals model state: a value that always exists, is read synchronously, and notifies dependants. computed() is lazy, cached and glitch-free; effect() is for side effects.
  • RxJS models events over time: streams that may emit many times or never, with operators for time (debounceTime), concurrency and cancellation (switchMap), retries and combination.
  • The practical split: signals in components and templates; RxJS where time, cancellation or event streams matter (search boxes, websockets, polling, complex HTTP orchestration).
  • The bridge: toSignal() and toObservable() from @angular/core/rxjs-interop; rxResource(), resource() and httpResource() (stable in v22) for async data driven by signals.
  • Differences that bite: a BehaviorSubject re-emits equal values while a signal skips them; combineLatest can emit an intermediate inconsistent value where computed() cannot.
  • Neither replaces the other: HttpClient, router events and forms still expose Observables.

Clarifying questions expected:

  • "Is the goal simpler templates and change detection, or removing the RxJS dependency?"
  • "Which parts are event-heavy, such as search, websockets or polling?"

Code / implementation expected: Yes — a search box where the query is a signal, RxJS handles debounce and cancellation, and the result comes back as a signal.

  • signals
  • rxjs
  • reactivity

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read · 10 sections

Read the code

AngularSignal state, RxJS for debounce and cancellation, signal back to the template
import { Component, computed, signal } from "@angular/core";
import { toObservable, toSignal } from "@angular/core/rxjs-interop";
import { Observable, debounceTime, distinctUntilChanged, switchMap, map, timer, startWith } from "rxjs";

const CITIES = ["London", "Lisbon", "Lima", "Lagos", "Leeds", "Paris", "Prague"];

// Pretend HTTP call: async, cancellable, takes 150ms.
function searchApi(term: string): Observable<string[]> {
  return new Observable<string[]>((sub) => {
    console.log(`request "${term}"`);
    let done = false;
    const id = setTimeout(() => {
      done = true;
      sub.next(CITIES.filter((c) => c.toLowerCase().startsWith(term.toLowerCase())));
      sub.complete();
    }, 150);
    // switchMap unsubscribes (runs this) when a newer term arrives first
    return () => { if (!done) { clearTimeout(id); console.log(`cancelled "${term}"`); } };
  });
}

@Component({
  selector: "app-root",
  template: `
    <input placeholder="City" [value]="query()" (input)="query.set($any($event.target).value)" />
    <p>{{ status() }}</p>
    <ul>@for (c of results(); track c) { <li>{{ c }}</li> }</ul>
  `,
})
export class AppComponent {
  // STATE: a signal. Synchronous, always has a value, read in the template.
  query = signal("");

  // EVENTS OVER TIME: RxJS. Debounce, drop duplicates, cancel stale requests.
  private results$ = toObservable(this.query).pipe(
    debounceTime(100),
    map((q) => q.trim()),
    distinctUntilChanged(),
    switchMap((q) => (q ? searchApi(q).pipe(startWith(null)) : [[]])),
  );

  // Back to STATE for the template.
  private raw = toSignal(this.results$, { initialValue: [] as string[] | null });
  loading = computed(() => this.raw() === null);
  results = computed(() => this.raw() ?? []);
  status = computed(() => (this.loading() ? "Searching..." : `${this.results().length} result(s)`));
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 87 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextHard

How do you build a custom form control by implementing the ControlValueAccessor interface?

Continue