The @angular/rxjs-interop
package offers APIs that help you integrate RxJS and Angular signals.
Create a signal from an RxJs Observable with toSignal
Use the toSignal
function to create a signal which tracks the value of an Observable. It behaves similarly to the async
pipe in templates, but is more flexible and can be used anywhere in an application.
import { Component } from '@angular/core';import { AsyncPipe } from '@angular/common';import { interval } from 'rxjs';import { toSignal } from '@angular/core/rxjs-interop';@Component({ template: `{{ counter() }}`,})export class Ticker { counterObservable = interval(1000); // Get a `Signal` representing the `counterObservable`'s value. counter = toSignal(this.counterObservable, {initialValue: 0});}
Like the async
pipe, toSignal
subscribes to the Observable immediately, which may trigger side effects. The subscription created by toSignal
automatically unsubscribes from the given Observable when the component or service which calls toSignal
is destroyed.
IMPORTANT: toSignal
creates a subscription. You should avoid calling it repeatedly for the same Observable, and instead reuse the signal it returns.
Injection context
toSignal
by default needs to run in an injection context, such as during construction of a component or service. If an injection context is not available, you can manually specify the Injector
to use instead.
Initial values
Observables may not produce a value synchronously on subscription, but signals always require a current value. There are several ways to deal with this "initial" value of toSignal
signals.
The initialValue
option
As in the example above, you can specify an initialValue
option with the value the signal should return before the Observable emits for the first time.
undefined
initial values
If you don't provide an initialValue
, the resulting signal will return undefined
until the Observable emits. This is similar to the async
pipe's behavior of returning null
.
The requireSync
option
Some Observables are guaranteed to emit synchronously, such as BehaviorSubject
. In those cases, you can specify the requireSync: true
option.
When requiredSync
is true
, toSignal
enforces that the Observable emits synchronously on subscription. This guarantees that the signal always has a value, and no undefined
type or initial value is required.
manualCleanup
By default, toSignal
automatically unsubscribes from the Observable when the component or service that creates it is destroyed.
To override this behavior, you can pass the manualCleanup
option. You can use this setting for Observables that complete themselves naturally.
Error and Completion
If an Observable used in toSignal
produces an error, that error is thrown when the signal is read.
If an Observable used in toSignal
completes, the signal continues to return the most recently emitted value before completion.
Create an RxJS Observable from a signal with toObservable
Use the toObservable
utility to create an Observable
which tracks the value of a signal. The signal's value is monitored with an effect
which emits the value to the Observable when it changes.
import { Component, signal } from '@angular/core';import { toObservable } from '@angular/core/rxjs-interop';@Component(/* ... */)export class SearchResults { query: Signal<string> = inject(QueryService).query; query$ = toObservable(this.query); results$ = this.query$.pipe( switchMap(query => this.http.get('/search?q=' + query )) );}
As the query
signal changes, the query$
Observable emits the latest query and triggers a new HTTP request.
Injection context
toObservable
by default needs to run in an injection context, such as during construction of a component or service. If an injection context is not available, you can manually specify the Injector
to use instead.
Timing of toObservable
toObservable
uses an effect to track the value of the signal in a ReplaySubject
. On subscription, the first value (if available) may be emitted synchronously, and all subsequent values will be asynchronous.
Unlike Observables, signals never provide a synchronous notification of changes. Even if you update a signal's value multiple times, toObservable
will only emit the value after the signal stabilizes.
const obs$ = toObservable(mySignal);obs$.subscribe(value => console.log(value));mySignal.set(1);mySignal.set(2);mySignal.set(3);
Here, only the last value (3) will be logged.
Using rxResource
for async data
IMPORTANT: rxResource
is experimental. It's ready for you to try, but it might change before it is stable.
Angular's resource
function gives you a way to incorporate async data into your application's signal-based code. Building on top of this pattern, rxResource
lets you define a resource where the source of your data is defined in terms of an RxJS Observable
. Instead of accepting a loader
function, rxResource
accepts a stream
function that accepts an RxJS Observable
.
import {Component, inject} from '@angular/core';import {rxResource} from '@angular/core/rxjs-interop';@Component(/* ... */)export class UserProfile { // This component relies on a service that exposes data through an RxJS Observable. private userData = inject(MyUserDataClient); protected userId = input<string>(); private userResource = rxResource({ params: () => this.userId(), // The `stream` property expects a factory function that returns // a data stream as an RxJS Observable. stream: ({userId}) => this.userData.load(userId), });}
The stream
property accepts a factory function for an RxJS Observable
. This factory function is passed the resource's params
value and returns an Observable
. The resource calls this factory function every time the params
computation produces a new value. See Resource loaders for more details on the parameters passed to the factory function.
In all other ways, rxResource
behaves like and provides the same APIs as resource
for specifying parameters, reading values, checking loading state, and examining errors.