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

от автора

С выпуском 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *