diff --git a/skills/dev-skills/angular-developer/SKILL.md b/skills/dev-skills/angular-developer/SKILL.md index 6ab0e3d77009..286df22077b4 100644 --- a/skills/dev-skills/angular-developer/SKILL.md +++ b/skills/dev-skills/angular-developer/SKILL.md @@ -60,6 +60,7 @@ When managing state and data reactivity, use Angular Signals and consult the fol - **Dependent State (`linkedSignal`)**: Creating writable state linked to source signals. Read [linked-signal.md](references/linked-signal.md) - **Async Reactivity (`resource`)**: Fetching asynchronous data directly into signal state. Read [resource.md](references/resource.md) - **Side Effects (`effect`)**: Logging, third-party DOM manipulation (`afterRenderEffect`), and when NOT to use effects. Read [effects.md](references/effects.md) +- **RxJS and Signals Interoperability**: Bridging the gap between Observables and Signals using `toSignal`, `toObservable`, and `takeUntilDestroyed`. Read [rxjs-interop.md](references/rxjs-interop.md) ## HTTP Communication diff --git a/skills/dev-skills/angular-developer/references/rxjs-interop.md b/skills/dev-skills/angular-developer/references/rxjs-interop.md new file mode 100644 index 000000000000..d7d403e3e7f0 --- /dev/null +++ b/skills/dev-skills/angular-developer/references/rxjs-interop.md @@ -0,0 +1,157 @@ +# RxJS and Signals Interoperability + +Angular provides a unified reactive model by bridging **RxJS** (ideal for asynchronous event streams) and **Signals** (ideal for synchronous application state and template rendering). + +Use the `@angular/core/rxjs-interop` package to convert between the two models and manage subscriptions cleanly. + +--- + +## Converting Observables to Signals (`toSignal`) + +Use `toSignal` to read values from an Observable as a reactive Signal. This allows you to bind asynchronous streams directly to templates without using the `async` pipe. + +```ts +import {Component, inject} from '@angular/core'; +import {toSignal} from '@angular/core/rxjs-interop'; +import {UserService} from './user.service'; + +@Component({ + selector: 'app-user-profile', + template: ` + @if (user()) { +
Welcome, {{ user()?.name }}
+ } + `, +}) +export class UserProfile { + private readonly userService = inject(UserService); + + // Convert the Observable stream to a read-only Signal + readonly user = toSignal(this.userService.getCurrentUser()); +} +``` + +- **Subscription Management**: `toSignal` automatically subscribes to the Observable immediately and unsubscribes when the containing component or service is destroyed. +- **Initial Value**: By default, the resulting signal returns `undefined` before the Observable emits its first value. Use the `initialValue` option to set a default: + ```ts + readonly user = toSignal(this.userService.getCurrentUser(), {initialValue: {name: 'Guest'}}); + ``` +- **Synchronous Observables**: If the Observable emits synchronously upon subscription (e.g. a `BehaviorSubject`), pass `{ requireSync: true }` to avoid an `undefined` initial type: + ```ts + readonly theme = toSignal(this.themeService.theme$, {requireSync: true}); + ``` +- **Error Handling**: If the Observable emits an error, reading the signal will throw that error. You can catch the error using standard try/catch or an error boundary. + +--- + +## Converting Signals to Observables (`toObservable`) + +Use `toObservable` to track changes to a Signal and pipe them into RxJS operators. This is highly useful for reacting to state changes and triggering asynchronous operations (like search auto-complete). + +```ts +import {Component, inject, signal} from '@angular/core'; +import {toObservable, toSignal} from '@angular/core/rxjs-interop'; +import {debounceTime, distinctUntilChanged, switchMap} from 'rxjs/operators'; +import {SearchService} from './search.service'; + +@Component({ + selector: 'app-search', + template: ` + +