Модуль 4 · урок 12 з 13
RxJS і сигнали разом
Сигнали й RxJS — не конкуренти. У сучасному Angular вони живуть разом: асинхронність описують потоками, стан для шаблону тримають у сигналах. Дві функції зшивають ці світи, і після цього уроку в тебе буде чітке правило, що де використовувати.
toSignal — з потоку в сигнал
import { toSignal } from '@angular/core/rxjs-interop';
export class TasksPage {
private readonly store = inject(TaskStore);
// підписка створюється й прибирається автоматично
readonly tasks = toSignal(this.store.visible$, { initialValue: [] });
readonly loading = toSignal(this.store.loading$, { initialValue: false });
}
@for (task of tasks(); track task.id) { … } <!-- без async pipe -->
Що дає toSignal:
- підписка живе рівно стільки, скільки контекст інʼєкції — відписуватись не треба;
- у шаблоні значення читається як функція, без
| asyncі без?? []у кожному місці; - значення доступне синхронно в коді, а не лише в шаблоні.
// варіанти початкового значення
toSignal(source$, { initialValue: [] }); // тип Task[]
toSignal(source$); // тип Task[] | undefined
toSignal(source$, { requireSync: true }); // для BehaviorSubject: значення є одразу
toObservable — із сигналу в потік
import { toObservable } from '@angular/core/rxjs-interop';
export class TaskSearch {
readonly query = signal('');
readonly results = toSignal(
toObservable(this.query).pipe( // сигнал → потік
debounceTime(300),
distinctUntilChanged(),
switchMap(q => this.api.search(q)),
),
{ initialValue: [] }, // потік → сигнал
);
}
Ось найкорисніший приклад інтеропу: стан лишається сигналом (простим і синхронним), а вся асинхронна логіка — затримка, дедуплікація, скасування — описана операторами RxJS. Кожен інструмент робить те, у чому сильний.
Правило вибору
| Задача | Інструмент |
|---|---|
| Значення, яке просто є: фільтр, вибраний рядок, режим теми | signal |
| Похідне значення | computed |
| Затримка, дедуплікація, скасування попереднього запиту | RxJS |
| Комбінація кількох асинхронних джерел | RxJS |
| Події в часі: WebSocket, клавіатура, скрол | RxJS |
| Дані для шаблону з асинхронного джерела | toSignal або async pipe |
resource і httpResource
import { resource } from '@angular/core';
export class TaskDetail {
readonly id = input.required<string>();
readonly task = resource({
params: () => ({ id: this.id() }), // від чого залежить
loader: ({ params, abortSignal }) => // як завантажити
fetch(`/api/tasks/${params.id}`, { signal: abortSignal }).then(r => r.json()),
});
}
@if (task.isLoading()) { <app-skeleton /> }
@else if (task.error()) { <app-error /> }
@else { <app-task [data]="task.value()" /> }
resource — сигнальний спосіб завантажувати дані: він сам скасовує попередній
запит при зміні параметрів і сам віддає три стани — isLoading, error,
value. Тобто робить те, що ти вручну складав із switchMap,
catchError і finalize.
У таблиці ліда прямо названі BehaviorSubject, combineLatest
і switchMap — тож практику модуля роби саме на RxJS. Але знати про
resource варто: якщо на дзвінку спитають «а як би ти зробив це сьогодні»,
згадка про сигнальні ресурси покаже, що ти стежиш за тим, куди рухається фреймворк.