Angular's RxJS interoperability APIs provide seamless integration between Angular's signal-based reactivity system and RxJS observables, enabling conversion between signals and observables in both directions.
Convert signals to observables for integration with RxJS-based code and reactive programming patterns.
/**
* Converts a signal to an Observable
* @param source - Signal to convert to Observable
* @param options - Optional configuration
* @returns Observable that emits signal values
*/
function toObservable<T>(
source: Signal<T>,
options?: {
injector?: Injector;
}
): Observable<T>;Convert observables to signals for integration with Angular's signal-based reactivity system.
/**
* Converts an Observable to a Signal
* @param source - Observable to convert to Signal
* @param options - Optional configuration
* @returns Signal that reflects Observable values
*/
function toSignal<T>(
source: Observable<T>,
options: {
initialValue?: undefined;
injector?: Injector;
manualCleanup?: boolean;
rejectErrors?: boolean;
} & ({requireSync: true} | {initialValue: T})
): Signal<T>;
/**
* Converts an Observable to a Signal with initial value
* @param source - Observable to convert to Signal
* @param options - Configuration with initial value
* @returns Signal that reflects Observable values
*/
function toSignal<T>(
source: Observable<T>,
options: {
initialValue: T;
injector?: Injector;
manualCleanup?: boolean;
rejectErrors?: boolean;
}
): Signal<T>;
/**
* Converts an Observable to a Signal without initial value
* @param source - Observable to convert to Signal
* @param options - Optional configuration
* @returns Signal that may be undefined initially
*/
function toSignal<T>(
source: Observable<T>,
options?: {
initialValue?: undefined;
injector?: Injector;
manualCleanup?: boolean;
rejectErrors?: boolean;
}
): Signal<T | undefined>;RxJS operators and utilities for integration with Angular component lifecycle.
/**
* RxJS operator that completes the stream when component is destroyed
* @param destroyRef - Optional DestroyRef, will be injected if not provided
* @returns MonoTypeOperatorFunction that completes on destroy
*/
function takeUntilDestroyed<T>(destroyRef?: DestroyRef): MonoTypeOperatorFunction<T>;Create Angular outputs from observables and convert outputs to observables.
/**
* Creates an Angular output from an Observable
* @param source - Observable to convert to output
* @param options - Optional configuration
* @returns OutputRef for the observable
*/
function outputFromObservable<T>(
source: Observable<T>,
options?: {
injector?: Injector;
}
): OutputRef<T>;
/**
* Converts an Angular output to an Observable
* @param output - Output to convert to Observable
* @param options - Optional configuration
* @returns Observable of output events
*/
function outputToObservable<T>(
output: OutputRef<T>,
options?: {
injector?: Injector;
}
): Observable<T>;Create resources from observables for server-side rendering and data fetching patterns.
/**
* Creates a resource from an Observable
* @param source - Observable to create resource from
* @param options - Optional configuration
* @returns Resource wrapping the observable
*/
function rxResource<T>(
source: Observable<T>,
options?: {
injector?: Injector;
}
): Resource<T>;Utilities for handling events in reactive contexts.
/**
* Creates a pending state that resolves when specified event occurs
* @param target - Event target (element, component, etc.)
* @param eventName - Name of event to wait for
* @param options - Optional configuration
* @returns Promise that resolves when event occurs
*/
function pendingUntilEvent<T = Event>(
target: EventTarget,
eventName: string,
options?: {
injector?: Injector;
}
): Promise<T>;import { Component, signal, inject } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-search',
template: `
<div>
<input
[(ngModel)]="searchTerm"
(input)="updateSearchTerm($event)"
placeholder="Search users..."
>
<ul>
<li *ngFor="let user of searchResults()">
{{user.name}} - {{user.email}}
</li>
</ul>
</div>
`
})
export class SearchComponent {
private http = inject(HttpClient);
// Signal for search term
private searchTermSignal = signal('');
// Convert signal to observable for RxJS operations
private searchTerm$ = toObservable(this.searchTermSignal);
// Use RxJS operators for debouncing and API calls
private searchResults$ = this.searchTerm$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term =>
term ? this.http.get<User[]>(`/api/users?search=${term}`) : []
)
);
// Convert result back to signal
searchResults = toSignal(this.searchResults$, { initialValue: [] });
updateSearchTerm(event: Event): void {
const target = event.target as HTMLInputElement;
this.searchTermSignal.set(target.value);
}
}import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';
import { interval, combineLatest } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
interface WeatherData {
temperature: number;
humidity: number;
conditions: string;
}
@Component({
selector: 'app-weather',
template: `
<div>
<h3>Current Weather</h3>
<div *ngIf="weather(); else loading">
<p>Temperature: {{weather()?.temperature}}°F</p>
<p>Humidity: {{weather()?.humidity}}%</p>
<p>Conditions: {{weather()?.conditions}}</p>
<p>Last updated: {{lastUpdate() | date:'medium'}}</p>
</div>
<ng-template #loading>
<p>Loading weather data...</p>
</ng-template>
</div>
`
})
export class WeatherComponent {
private http = inject(HttpClient);
// Observable that refreshes every 5 minutes
private refreshTimer$ = interval(5 * 60 * 1000).pipe(startWith(0));
// Observable for weather data
private weatherData$ = this.refreshTimer$.pipe(
switchMap(() => this.http.get<WeatherData>('/api/weather'))
);
// Convert to signals
weather = toSignal(this.weatherData$);
lastUpdate = toSignal(
this.refreshTimer$.pipe(map(() => new Date())),
{ initialValue: new Date() }
);
}import { Component, OnInit, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';
import { tap } from 'rxjs/operators';
@Component({
selector: 'app-timer',
template: `
<div>
<h3>Timer Component</h3>
<p>Elapsed: {{elapsed}} seconds</p>
<p>Status: {{status}}</p>
</div>
`
})
export class TimerComponent implements OnInit {
elapsed = 0;
status = 'Starting...';
ngOnInit(): void {
// Timer that automatically stops when component is destroyed
interval(1000)
.pipe(
takeUntilDestroyed(), // Automatically unsubscribes on destroy
tap(count => {
this.elapsed = count + 1;
this.status = 'Running';
})
)
.subscribe();
// Manual cleanup example with custom DestroyRef
const destroyRef = inject(DestroyRef);
interval(5000)
.pipe(
takeUntilDestroyed(destroyRef),
tap(() => console.log('Periodic log every 5 seconds'))
)
.subscribe();
}
}import { Component, signal } from '@angular/core';
import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
import { Subject, merge } from 'rxjs';
import { map, filter } from 'rxjs/operators';
@Component({
selector: 'app-custom-input',
template: `
<div>
<input
#input
(input)="handleInput($event)"
(keyup.enter)="handleEnter()"
(blur)="handleBlur()"
>
<p>Current value: {{value()}}</p>
</div>
`
})
export class CustomInputComponent {
private inputSubject = new Subject<string>();
private enterSubject = new Subject<string>();
private blurSubject = new Subject<string>();
// Signal for current value
value = signal('');
// Create outputs from observables
valueChange = outputFromObservable(
merge(
this.inputSubject,
this.enterSubject,
this.blurSubject
)
);
enterPressed = outputFromObservable(
this.enterSubject.pipe(
filter(value => value.length > 0),
map(() => ({ value: this.value(), timestamp: Date.now() }))
)
);
focusLost = outputFromObservable(this.blurSubject);
handleInput(event: Event): void {
const target = event.target as HTMLInputElement;
const value = target.value;
this.value.set(value);
this.inputSubject.next(value);
}
handleEnter(): void {
this.enterSubject.next(this.value());
}
handleBlur(): void {
this.blurSubject.next(this.value());
}
}import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { rxResource } from '@angular/core/rxjs-interop';
import { switchMap, retry, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
interface UserProfile {
id: number;
name: string;
email: string;
avatar: string;
}
@Component({
selector: 'app-user-profile',
template: `
<div>
<h3>User Profile</h3>
<div [ngSwitch]="userResource.status()">
<div *ngSwitchCase="'loading'">
<p>Loading user profile...</p>
</div>
<div *ngSwitchCase="'error'">
<p>Error loading profile: {{userResource.error()?.message}}</p>
<button (click)="refreshProfile()">Retry</button>
</div>
<div *ngSwitchCase="'resolved'">
<div class="profile">
<img [src]="userResource.value()?.avatar" [alt]="userResource.value()?.name">
<h4>{{userResource.value()?.name}}</h4>
<p>{{userResource.value()?.email}}</p>
<button (click)="refreshProfile()">Refresh</button>
</div>
</div>
</div>
</div>
`
})
export class UserProfileComponent {
private http = inject(HttpClient);
// Create resource from observable with error handling and retry
userResource = rxResource(
this.http.get<UserProfile>('/api/user/profile').pipe(
retry(3),
catchError(error => {
console.error('Failed to load user profile:', error);
throw error;
})
)
);
refreshProfile(): void {
// Resource will automatically re-execute the observable
this.userResource.reload();
}
}import { Component, ElementRef, viewChild, AfterViewInit } from '@angular/core';
import { pendingUntilEvent } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-loading-animation',
template: `
<div>
<img
#image
[src]="imageUrl"
[class.loaded]="imageLoaded"
alt="Content image"
>
<div *ngIf="!imageLoaded" class="loading-spinner">
Loading...
</div>
</div>
`,
styles: [`
img {
opacity: 0;
transition: opacity 0.3s ease;
}
img.loaded {
opacity: 1;
}
.loading-spinner {
display: inline-block;
/* spinner styles */
}
`]
})
export class LoadingAnimationComponent implements AfterViewInit {
imageRef = viewChild.required<ElementRef<HTMLImageElement>>('image');
imageUrl = 'https://example.com/large-image.jpg';
imageLoaded = false;
async ngAfterViewInit(): Promise<void> {
const img = this.imageRef().nativeElement;
try {
// Wait for image load event
await pendingUntilEvent(img, 'load');
this.imageLoaded = true;
console.log('Image loaded successfully');
} catch (error) {
// Handle error event
await pendingUntilEvent(img, 'error');
console.error('Image failed to load');
this.imageLoaded = false;
}
}
}