RxJS Interop в Angular 18: основные изменения и преимущества

С выпуском Angular 18 команда разработчиков расширила функциональность RxJS Interop, что значительно упрощает интеграцию между Signals и RxJS Observables, оптимизируя производительность и улучшая читаемость кода. В этой статье мы рассмотрим, что такое RxJS Interop и как он влияет на разработку на Angular.

Эволюция RxJS Interop в Angular

a957f0a6f4158b48f2a5968b6f46b7ef.png

RxJS Interop впервые был представлен в Angular 16 для преодоления разрыва между Signals и RxJS Observables. Первая версия позволяла разработчикам конвертировать Signals в Observables и наоборот. В Angular 17 функциональность была улучшена, чтобы сделать преобразования более эффективными и обеспечить лучшую интеграцию операторов RxJS с Signals.

Теперь, с Angular 18, функция RxJS Interop вышла на новый уровень, предлагая улучшенную поддержку операторов, более качественные преобразования и более гибкие настройки, что делает управление реактивным состоянием ещё более удобным.

Что такое RxJS Interop?

e3eb8285dce4a4f8d922de1df2d47fcb.png

RxJS Interop позволяет разработчикам Angular легко комбинировать и конвертировать Signals и Observables. Традиционно Angular активно использовал RxJS для обработки асинхронных операций, таких как HTTP-запросы или пользовательские события. С появлением Signals Angular предлагает ещё один способ управления реактивным состоянием.

RxJS Interop позволяет:

  • Конвертировать Signals в Observables и наоборот.

  • Использовать операторы RxJS, такие как map, filter и merge, с Signals.

  • Упрощать работу с реактивными данными.

  • Использовать метод outputToObservable для прямых преобразований.

  • Применять гибкие настройки конверсии.

  • Управлять освобождением ресурсов с помощью manualCleanup.

  • Задавать начальное значение для Signals из Observables с помощью initialValue.

Эта функция предоставляет разработчикам больше возможностей для управления реактивными паттернами, сохраняя при этом поддерживаемость приложений.

Краткий обзор RxJS в Angular

RxJS лежит в основе реактивной системы Angular, предоставляя тип Observable для управления асинхронными данными. Он используется во многих частях Angular, таких как HttpClient, формы и обработка событий.

  • Observable: поток данных, который генерирует множество значений с течением времени.

  • Subject: Observable, который рассылает значения нескольким наблюдателям.

  • BehaviorSubject: хранит последнее значение и немедленно отправляет его новым подписчикам.

  • ReplaySubject: повторяет буфер предыдущих значений для новых подписчиков.

Эти паттерны мощные, но могут быть сложными при управлении состоянием или обработке нескольких асинхронных операций. Signals в сочетании с RxJS Interop помогают упростить такие сценарии.

RxJS Interop в Angular 18

Signals vs Observables: когда использовать что?

С Angular 18 разработчики получают больше гибкости в выборе между Signals и Observables:

  • Observables идеальны для непрерывных событийных данных, таких как HTTP-запросы или события форм.

  • Signals лучше подходят для реактивного состояния с предсказуемыми потоками данных.

RxJS Interop делает интеграцию между этими двумя системами бесшовной, уменьшая количество шаблонного кода и повышая возможность повторного использования кода.

Основные функции RxJS Interop

Конвертация Signals в Observables

С RxJS Interop вы можете конвертировать Signal в Observable и использовать операторы RxJS, такие как map или debounceTime, для преобразования данных. Также можно применять опции, такие как requireSync, для синхронного поведения:

import { Component } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { createSignal } from '@angular/core';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-root',
  template: `
    

Signal Value: {{ mySignal }}

` }) export class AppComponent { private _mySignal = createSignal(0); myObservable: Observable; constructor() { this.myObservable = toObservable(this._mySignal, { requireSync: true }); } get mySignal() { return this._mySignal(); } incrementSignal() { this._mySignal.set(this._mySignal() + 1); } }

Конвертация Observables в Signals с manualCleanup и initialValue

Вы можете конвертировать Observable в Signal, упростив управление состоянием, и использовать manualCleanup и initialValue для большего контроля:

import { Component, OnDestroy } from '@angular/core';
import { fromObservable } from '@angular/core/rxjs-interop';
import { Observable, of } from 'rxjs';

@Component({
  selector: 'app-root',
  template: `
    

Signal from Observable: {{ mySignal }}

` }) export class AppComponent implements OnDestroy { myObservable: Observable = of(42); private _mySignal; private cleanupFn: () => void; constructor() { const [signal, cleanup] = fromObservable(this.myObservable, { initialValue: 0, manualCleanup: true }); this._mySignal = signal; this.cleanupFn = cleanup; } get mySignal() { return this._mySignal(); } ngOnDestroy() { this.cleanupFn(); } }

Использование outputToObservable

outputToObservable позволяет конвертировать выходные данные компонента Angular в Observables:

import { Component, EventEmitter, Output } from '@angular/core';
import { outputToObservable } from '@angular/core/rxjs-interop';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-root',
  template: `
    

Check console for button click events

` }) export class AppComponent { @Output() buttonClicked = new EventEmitter(); buttonClicked$: Observable; constructor() { this.buttonClicked$ = outputToObservable(this, 'buttonClicked'); } onButtonClick() { this.buttonClicked.emit(); } }

Использование операторов RxJS с Signals

Операторы RxJS могут применяться непосредственно к Signals, упрощая код, улучшая производительность и упрощая отладку:

import { Component } from '@angular/core';
import { createSignal } from '@angular/core';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  template: `
    

Mapped Signal Value: {{ mappedSignal | async }}

` }) export class AppComponent { private _mySignal = createSignal(0); get mappedSignal() { return this._mySignal().pipe(map(value => value * 2)); } get mySignal() { return this._mySignal(); } incrementSignal() { this._mySignal.set(this._mySignal() + 1); } }

Переход к синхронной реактивности с Signals

Хотя RxJS Interop в Angular 18 обеспечивает мощную гибкость, он также открывает дверь к новому подходу — отказу от асинхронной реактивности и переходу к синхронной реактивности с Signals. Возможность эффективно использовать Signals снижает сложность за счёт управления состоянием синхронно, устраняя необходимость в шаблонном коде, связанном с асинхронностью.

Почему стоит рассмотреть синхронную реактивность?

Традиционный подход с использованием Observables и RxJS отлично подходит для обработки сложных асинхронных задач, таких как HTTP-запросы или взаимодействия с UI. Однако этот подход часто включает сложное управление подписками и потенциальные утечки памяти. Переход на Signals позволяет упростить управление реактивным состоянием.

С Signals поток данных проще для понимания, а обновления происходят синхронно, что позволяет разработчикам полагаться на немедленное отражение изменений без необходимости управления асинхронными временными интервалами. Этот сдвиг особенно полезен в сценариях с предсказуемыми и управляемыми потоками данных, что снижает сложность управления Observables.

Пример: Замена RxJS на Signals в сценарии с Firebase

952e1b3aa2c4dcde0d041deafd42accd.png

Рассмотрим пример, аналогичный тому, как можно управлять взаимодействием с Firebase. Традиционно разработчики используют RxJS для обработки обновлений в реальном времени и управления подписками, но с Signals мы можем заменить этот паттерн на более простой, синхронный подход.

Пример использования синхронных Signals:

import { Component } from '@angular/core';
import { createSignal } from '@angular/core';
import { Firestore, collectionData, collection } from '@angular/fire/firestore';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-root',
  template: `
    

Items

  • {{ item.name }}
` }) export class AppComponent { private _dataSignal = createSignal([]); data$!: Observable; constructor(private firestore: Firestore) { const col = collection(firestore, 'items'); this.data$ = collectionData(col); // Вместо подписки на Observable, мы можем установить значение Signal напрямую. this.data$.subscribe(data => { this._dataSignal.set(data); }); } get data() { return this._dataSignal(); } }

В этом примере вместо обработки асинхронных подписок и ручного управления состоянием Signal обновляется синхронно при поступлении новых данных. Это делает компонент проще, понятнее и легче в обслуживании.

Заключение

Новый RxJS Interop в Angular предоставляет мост для перехода между реактивными парадигмами, а также открывает путь к синхронной реактивности с использованием Signals. Используя Signals, вы можете упростить архитектуру приложения, снизить сложность управления асинхронными потоками и создать более предсказуемый и поддерживаемый код. Для многих сценариев управления состоянием синхронная реактивность оказывается более чем достаточной и помогает сосредоточиться на самом важном — создании качественного пользовательского опыта.

© Habrahabr.ru