С выпуском Angular 18 команда разработчиков расширила функциональность RxJS Interop, что значительно упрощает интеграцию между Signals и RxJS Observables, оптимизируя производительность и улучшая читаемость кода. В этой статье мы рассмотрим, что такое RxJS Interop и как он влияет на разработку на Angular.
Эволюция RxJS Interop в Angular
RxJS Interop впервые был представлен в Angular 16 для преодоления разрыва между Signals и RxJS Observables. Первая версия позволяла разработчикам конвертировать Signals в Observables и наоборот. В Angular 17 функциональность была улучшена, чтобы сделать преобразования более эффективными и обеспечить лучшую интеграцию операторов RxJS с Signals.
Теперь, с Angular 18, функция RxJS Interop вышла на новый уровень, предлагая улучшенную поддержку операторов, более качественные преобразования и более гибкие настройки, что делает управление реактивным состоянием ещё более удобным.
Что такое RxJS Interop?
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: ` <div> <button (click)="incrementSignal()">Increment Signal</button> <p>Signal Value: {{ mySignal }}</p> </div> ` }) export class AppComponent { private _mySignal = createSignal(0); myObservable: Observable<number>; 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: ` <div> <p>Signal from Observable: {{ mySignal }}</p> </div> ` }) export class AppComponent implements OnDestroy { myObservable: Observable<number> = 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: ` <div> <button (click)="onButtonClick()">Click Me</button> <p>Check console for button click events</p> </div> ` }) export class AppComponent { @Output() buttonClicked = new EventEmitter<void>(); buttonClicked$: Observable<void>; 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'; @UntilDestroy() @Component({ selector: 'app-root', template: ` <div> <button (click)="incrementSignal()">Increment Signal</button> <p>Mapped Signal Value: {{ mappedSignal | async }}</p> </div> ` }) 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
Рассмотрим пример, аналогичный тому, как можно управлять взаимодействием с Firebase. Традиционно разработчики используют RxJS для обработки обновлений в реальном времени и управления подписками, но с Signals мы можем заменить этот паттерн на более простой, синхронный подход.
Пример использования синхронных Signals:
import { Component } from '@angular/core'; import { createSignal } from '@angular/core'; import { Firestore, collectionData, collection } from '@angular/fire/firestore'; import { Observable, Subject } from 'rxjs'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; @UntilDestroy() @Component({ selector: 'app-root', template: ` <div> <h2>Items</h2> <ul> <li *ngFor="let item of data">{{ item.name }}</li> </ul> </div> ` }) export class AppComponent { private destroy$ = new Subject(); private _dataSignal = createSignal<any[]>([]); data$!: Observable<any[]>; constructor(private firestore: Firestore) { const col = collection(firestore, 'items'); this.data$ = collectionData(col); // Вместо подписки на Observable, мы можем установить значение Signal напрямую. this.data$.pipe(untilDestroyed(this)).subscribe(data => { this._dataSignal.set(data); }); } get data() { return this._dataSignal(); } }
В этом примере вместо обработки асинхронных подписок и ручного управления состоянием Signal обновляется синхронно при поступлении новых данных. Это делает компонент проще, понятнее и легче в обслуживании.
Заключение
Новый RxJS Interop в Angular предоставляет мост для перехода между реактивными парадигмами, а также открывает путь к синхронной реактивности с использованием Signals. Используя Signals, вы можете упростить архитектуру приложения, снизить сложность управления асинхронными потоками и создать более предсказуемый и поддерживаемый код. Для многих сценариев управления состоянием синхронная реактивность оказывается более чем достаточной и помогает сосредоточиться на самом важном — создании качественного пользовательского опыта.
ссылка на оригинал статьи https://habr.com/ru/articles/851516/
Добавить комментарий