Angular IDPStrong Junior
Модулі / Дані, моки й RxJS / RxJS і сигнали разом
🔥 0

Модуль 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 варто: якщо на дзвінку спитають «а як би ти зробив це сьогодні», згадка про сигнальні ресурси покаже, що ти стежиш за тим, куди рухається фреймворк.