AngularAtlased. 22.2

Ch. 03Signals & reactivity20 / 75

Signals ↔ RxJS

toSignal, toObservable, takeUntilDestroyed, outputFromObservable — and when RxJS remains the right choice.

~0 min read Official docs

Signals don’t replace RxJS — they complement it. Signals are excellent for state (what is shown now), RxJS for events over time (what happens and when). The package @angular/core/rxjs-interop has the bridges between the two worlds. New to Observables? Start with Observables from zero.

SignalsRxJS / Observable
Always have a current valueEmit values over time (they may not have emitted yet)
Synchronous: count()Asynchronous: subscribe()
No unsubscribing neededNeed unsubscribing (or an operator that completes)
Derivation: computed()Derivation: map, combineLatest, switchMap…
Ideal for templates and UI stateIdeal for websockets, drag, retry, complex debounce

toSignal(): Observable → Signal

import { toSignal } from '@angular/core/rxjs-interop';

export class Clock {
  // Automatic subscription + automatic unsubscription when the component is destroyed
  readonly seconds = toSignal(interval(1000), { initialValue: 0 });

  // Without initialValue → the type is T | undefined
  readonly user = toSignal(this.auth.user$);            // Signal<User | undefined>

  // requireSync: the observable emits synchronously (BehaviorSubject, startWith) → no undefined
  readonly theme = toSignal(this.theme$, { requireSync: true }); // Signal<Theme>

  // Route params as a signal (if you don’t use withComponentInputBinding)
  private readonly route = inject(ActivatedRoute);
  readonly id = toSignal(this.route.paramMap.pipe(map((p) => Number(p.get('id')))), {
    initialValue: 0,
  });
}

toObservable(): Signal → Observable

import { toObservable } from '@angular/core/rxjs-interop';

export class ProductSearch {
  readonly query = signal('');

  // signal → observable → RxJS operators → back to a signal
  readonly results = toSignal(
    toObservable(this.query).pipe(
      debounceTime(300),
      distinctUntilChanged(),
      filter((q) => q.length >= 2),
      switchMap((q) => this.api.search(q).pipe(catchError(() => of([])))),
    ),
    { initialValue: [] },
  );
}
// toObservable emits ASYNCHRONOUSLY (through an effect) and only the final value
// if the signal is set several times in the same tick.
LivetoSignal and toObservable
toSignal(interval(1000))0 s
toSignal(fromEvent(window, 'resize'))1024 px
computed over bothdesktop
toObservable(count).pipe(pairwise()) → press the button
Resize the window and press the button: the signals derived from Observables update by themselves.

Where RxJS stays better

A mousemove stream with throttle and accumulation (scan) is trivial in RxJS and awkward with signals. You put the final result into a signal for the template.

LiveEvent streams: throttleTime + scan
Move the mouse here
Position: 0, 0 · events processed (throttle 40 ms): 0
Only one event every 40 ms is processed; the last 14 positions are accumulated with scan().

takeUntilDestroyed(): goodbye Subject destroy$

Every manual subscribe() in a component must be stopped on destruction. takeUntilDestroyed() does this automatically using DestroyRef. (With toSignal you don’t need it — it unsubscribes by itself.)

import { DestroyRef, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

export class Notifications {
  private readonly destroyRef = inject(DestroyRef);

  constructor() {
    // ✓ in an injection context: no argument
    this.socket.messages$.pipe(takeUntilDestroyed()).subscribe((m) => this.show(m));
  }

  connectLater() {
    // ✓ outside the constructor: pass the DestroyRef explicitly
    this.poll$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe();
  }
}

// Before: Subject + ngOnDestroy
// private destroy$ = new Subject<void>();
// ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); }
// obs$.pipe(takeUntil(this.destroy$))
LiveMemory leak vs correct cleanup
  • Create a child, then destroy it and watch the log.
Create the child with a leak, then destroy it: the log keeps flowing — the subscription outlived the component.

Outputs from and to Observables

import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';

export class Search {
  private readonly query$ = new Subject<string>();

  // An output built from an Observable
  readonly queryChange = outputFromObservable(this.query$.pipe(debounceTime(300)));
}

// The other way round: you treat a component’s output as an Observable (e.g. in a directive)
const changes$ = outputToObservable(childComponentRef.instance.queryChange);

Architecture recommendation

// The architecture recommendation:
//   RxJS at the edges (I/O, events),  Signals for the displayed state.

@Service()
export class ChatStore {
  private readonly socket = inject(ChatSocket); // exposes Observable<Message>

  // a stream of events → accumulated with RxJS → exposed as a signal
  readonly messages = toSignal(
    this.socket.messages$.pipe(scan((all, m) => [...all, m].slice(-100), [] as Message[])),
    { initialValue: [] },
  );

  readonly unread = computed(() => this.messages().filter((m) => !m.read).length);
}

When YES, when NO

Stay with RxJS when
  • You have events over time: websocket, SSE, drag & drop, infinite scroll.
  • You need time/concurrency operators: switchMap, exhaustMap, retry with backoff, bufferTime.
  • You combine many asynchronous sources with complex rules.
  • You work with APIs that expose Observables (Router events, Reactive Forms valueChanges).
Switch to signals when
  • You hold UI state (filters, selection, toggles).
  • You derive values for the template — computed is simpler than combineLatest.
  • You do GETs that depend on state — httpResource.
  • You use a BehaviorSubject only as an “observable variable” — a signal is exactly that.

Common pitfalls

  • toSignal outside the injection context → an error. Create it as a field or pass { injector }.
  • toSignal on a “cold” HTTP Observable in a getter → a new request on every call. Create it once.
  • Errors: if the Observable errors, reading the signal throws. Catch the error with catchError first.
  • toObservable doesn’t emit synchronously: don’t rely on getting the value right after set().

Cheat sheet

toSignal(obs$, { initialValue })        // Observable → Signal (auto-unsubscribe)
toSignal(obs$, { requireSync: true })   // no undefined, if it emits synchronously
toObservable(sig)                       // Signal → Observable (emits asynchronously)
takeUntilDestroyed()                    // in an injection context
takeUntilDestroyed(destroyRef)          // anywhere
outputFromObservable(obs$)              // Observable → output()
outputToObservable(output)              // output() → Observable
rxResource({ params, stream })          // async data over an Observable → Resource
pendingUntilEvent()                     // keeps the application “unstable” (SSR) until the first emission

Ch. 03 · 0 of 8 complete

Finished this lesson?

Mark it and it shows up on your map: in the contents and on the home page.

Marks itself as done once you have read the whole lesson.

  1. signal() and computed()
  2. effect() — when YES, when NO
  3. linkedSignal() — editable derived state
  4. resource, rxResource, httpResource
  5. debounced() — delayed signals
  6. Lists: search, filter, sort, paginate
  7. Observables from zero
  8. Signals ↔ RxJS
See your whole map

All 75 pages · type to filter

  • 01What's new in Angular 21 & 22All the important changes in v21 and v22, briefly, with a link to the page that explains each one.Start
  • 01Your first app, step by stepA reading list built from an empty component to a finished app — signals, @for, inputs and outputs, a service and a form, one step at a time.Start
  • 01Quick decisions“Where do I put this?”, “Signal or Observable?”, “Shared or feature?” — short answers to everyday questions.Start
  • 01Angular interview questions60 real interview questions for junior, mid and senior, with the answer to give, the follow-up and the red flags. Plus a mock interview.Start
  • 02Standalone & bootstrapHow an app starts without NgModule: bootstrapApplication, app.config.ts, imports in the component.Modern fundamentals
  • 02Data binding & eventsInterpolation, property and attribute bindings, events with $event and key filters, and how [( )] is built — what goes into the DOM and what comes back.Modern fundamentals
  • 02Control flow: @if, @for, @switchThe block syntax that replaces *ngIf/*ngFor/ngSwitch, with track, @empty and exhaustive checks.Modern fundamentals
  • 02@let and the new template expressionsLocal variables with @let, arrow functions, spread, regex, instanceof, comments inside tags.Modern fundamentals
  • 02@defer — lazy loading in templatesLoad heavy components only when needed: triggers, prefetch, placeholder, loading, error.Modern fundamentals
  • 02Class & style bindingsWhy [class.x] and [style.x] replaced ngClass and ngStyle (with automatic migration from v21).Modern fundamentals
  • 02Component styles & encapsulation:host, the three encapsulation modes, theming with CSS variables, and why ::ng-deep is a last resort.Modern fundamentals
  • 02Pipes: when and howPure vs impure pipes, custom pipes, and how you choose between a pipe, computed() and a method.Modern fundamentals
  • 03signal() and computed()The reactive primitives: writable state, derived state, equality, untracked and immutability rules.Signals & reactivity
  • 03effect() — when YES, when NOSide effects synchronised with signals: localStorage, logging, imperative APIs. And their traps.Signals & reactivity
  • 03linkedSignal() — editable derived stateA signal that resets when its source changes, but that you can also set manually.Signals & reactivity
  • 03resource, rxResource, httpResourceAsync data as signals: status, value, error, reload, automatic cancellation. Stable since v22.Signals & reactivity
  • 03debounced() — delayed signalsThe “debounce” version of a signal, without RxJS. Perfect for search.Signals & reactivity
  • 03Lists: search, filter, sort, paginateA chain of computed signals that recalculates only what changed, with the filters kept in the URL so a link reopens the same view.Signals & reactivity
  • 03Observables from zeroWhat an Observable is, subscribe and unsubscribe, the operators you use every week, cold vs hot — and when a signal is enough.Signals & reactivity
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — and when RxJS remains the right choice.Signals & reactivity
  • 04input() and output()Inputs as signals (required, transform, alias) and typed outputs. Data down, events up.Components: communication
  • 04model() — two-way bindingBanana-in-a-box [( )] with a single API. When to use it, and when input + output is better.Components: communication
  • 04viewChild & contentChildQueries as signals: viewChild, viewChildren, contentChild, contentChildren, the read option.Components: communication
  • 04Lifecycle & DestroyRefWhich hooks still matter in the signals era, afterNextRender, DestroyRef and typed ngOnChanges.Components: communication
  • 04Content projectionng-content with select, multiple slots, default content and the trap of conditional projection.Components: communication
  • 04ng-template & ngTemplateOutletTemplates as values: render props in Angular, typed context and customising components.Components: communication
  • 05Smart vs DumbContainer vs presentational: who holds the state, who only displays, and how to split them properly.Component design
  • 05Generic components <T>A correctly typed DataTable<T> or Select<T>, with templates for cells.Component design
  • 05Dynamic componentsNgComponentOutlet, createComponent with inputBinding/outputBinding, and a dashboard built from config.Component design
  • 05Directives & host compositionAttribute directives, the host: {} metadata and the Directive Composition API (hostDirectives).Component design
  • 05Custom form controlsFormValueControl (Signal Forms) vs ControlValueAccessor: a star rating, in both variants.Component design
  • 05Change detectionOnPush is the default from v22, zone.js is gone from v21. What triggers rendering and how not to get burned.Component design
  • 06inject() & @Service()The new @Service() decorator from v22, inject() vs the constructor, and the injection context.Dependency Injection
  • 06Providers & the injector hierarchyRoot vs route vs component, useClass/useValue/useFactory/useExisting, and the optional/self/skipSelf modifiers.Dependency Injection
  • 06InjectionToken & provideX()Typed configuration, provideFeature() functions, makeEnvironmentProviders and initializers.Dependency Injection
  • 06injectAsync() — lazy servicesLoad a heavy service only when you need it, with prefetch on idle.Dependency Injection
  • 07Folder structurecore / shared / features, what goes into each, and what a feature looks like inside.Application architecture
  • 07Shared vs featureWhen to move a component into shared and (especially) when NOT to. A concrete checklist.Application architecture
  • 07Interfaces & modelsInline interface or separate file? DTO vs domain model, type vs interface, where to put them.Application architecture
  • 07State management with signalsSimple signal stores, facades, local vs global state, and when you need NgRx.Application architecture
  • 07Style guide & namingThe current official conventions: file names without suffixes, protected, readonly, member order.Application architecture
  • 07Barrels, aliases & boundariesindex.ts, path aliases, a feature’s public API and dependency rules that can be checked automatically.Application architecture
  • 07Errors: @boundary & ErrorHandlerThe new @boundary block from v22.2, the global ErrorHandler, HTTP errors and errors from resource.Application architecture
  • 08Modern routing & lazy loadingloadComponent, child routes, parameters as inputs and parameter inheritance (the new default in v22).Routing
  • 08Guards & resolverscanActivate, canMatch, canDeactivate and resolve as functions, with redirects via UrlTree / RedirectCommand.Routing
  • 09Signal Forms — the basicsForms built on signals: form(), [formField], schema-based validation. Stable since v22.Forms
  • 09Signal Forms — advancedCustom and cross-field validation, async/HTTP with debounce, dynamic lists, disabled/hidden, reusable schemas.Forms
  • 09Typed Reactive FormsTyped FormGroup, nonNullable, FormArray — and how they coexist with Signal Forms.Forms
  • 10HttpClient, interceptors & the API layerHttpClient without setup (v21), Fetch by default (v22), functional interceptors and an API layer with DTO mapping.HTTP & data
  • 11Performance — a checklistWhat really matters: OnPush + signals, track, @defer, lazy routes, images, bundles and hydration.Quality & performance
  • 11Testing with VitestVitest is the default runner from v21. TestBed, components with signals, services, HTTP and harnesses.Quality & performance
  • 11DevTools: look inside your appChrome DevTools and Angular DevTools step by step: what each panel shows, with exercises that work on this site.Quality & performance
  • 11Angular AriaHeadless, accessible components that you style however you like: tabs, accordion, menu, listbox...Quality & performance
  • 12SSR, prerender & render modesWhen to render on the server, when at build time and when only in the browser: RenderMode per route, app.routes.server.ts and the real costs.SSR & hydration
  • 12Hydration: event replay & incrementalHow the browser takes over server HTML without re-rendering it: full hydration, event replay, @defer (hydrate on …) and mismatch errors.SSR & hydration
  • 12Server-compatible codewindow doesn’t exist on the server: PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, the HTTP cache and PendingTasks.SSR & hydration
  • 13Micro frontendsWhen it is worth splitting an app into several deployments and when it is just complexity: Native Federation, Module Federation, iframes, Web Components.Architecture at scale
  • 13Web Components with Angular ElementscreateCustomElement: an Angular component used as an HTML tag anywhere, plus communication between apps through events and BroadcastChannel.Architecture at scale
  • 13Monorepo, libraries & boundariesA workspace with several apps and libraries: ng generate library, Nx, secondary entry points, publishing and dependency rules.Architecture at scale
  • 13Hexagonal architecture (ports & adapters)A domain without Angular, ports as abstract classes and interchangeable adapters: HTTP, localStorage or in-memory, without touching the UI.Architecture at scale
  • 14Signals under the hoodThe reactive graph: producers and consumers, push-pull, why computed is lazy and glitch-free, the diamond problem and when effects run.Advanced reactivity
  • 14Advanced RxJS: higher-order operatorsswitchMap, mergeMap, concatMap and exhaustMap on a live diagram, plus retry with backoff, shareReplay and how to avoid leaks.Advanced reactivity
  • 14NgRx SignalStoreLarge-scale state management on signals: withState, withComputed, withMethods, rxMethod, entities and hooks — and when a simple store is enough.Advanced reactivity
  • 14State machines with signalsNo more impossible states: a typed machine on signals, with transitions, guards and a live state diagram.Advanced reactivity
  • 15WebSockets: real time that survives the networkA typed socket as signals: reconnect with backoff and jitter, heartbeat, an outbox while offline, and SSR safety.Real time & offline
  • 15Optimistic updates & rollbackAn instant UI that stays honest: snapshot and rollback, out-of-order responses, idempotency keys.Real time & offline
  • 15Offline-first: Service Worker & cachingThe Angular Service Worker: caching strategies, update prompts and an outbox for changes made offline.Real time & offline
  • 16Web WorkersHeavy computation without blocking the UI: a real worker in the page, message passing and when it is not worth it.Advanced UI & performance
  • 16Huge lists: virtual scroll100,000 rows without lag: CDK virtual scroll, itemSize, trackBy and the alternatives (pagination, infinite scroll).Advanced UI & performance
  • 16Modern animations & View Transitionsanimate.enter and animate.leave instead of the deprecated @angular/animations, CSS transitions and View Transitions on navigation.Advanced UI & performance
  • 16Memory leaks: finding and fixing themSubscriptions, timers and listeners that outlive their component — how to spot them in DevTools and close them.Advanced UI & performance
  • 17Security: XSS, sanitisation & CSPWhat Angular does automatically against XSS, when bypassSecurityTrust becomes dangerous, plus CSP with nonce, Trusted Types and XSRF.Security & globalisation
  • 17Authentication: tokens & refreshAn interceptor that attaches the token, refreshes once on 401 and replays the queued requests — plus where you keep the token.Security & globalisation
  • 17OAuth 2.1 & OpenID Connect with PKCEThe authorization code flow with PKCE, step by step: verifier and challenge, state and nonce, the ID token, and why a BFF is safer.Security & globalisation
  • 17Internationalisation (i18n)$localize, the i18n attribute, ICU plural/select, a build per language vs runtime translations, and locale-based formatting.Security & globalisation