С выпуском 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, вы можете упростить архитектуру приложения, снизить сложность управления асинхронными потоками и создать более предсказуемый и поддерживаемый код. Для многих сценариев управления состоянием синхронная реактивность оказывается более чем достаточной и помогает сосредоточиться на самом важном — создании качественного пользовательского опыта.
xemos
1) С учётом того что EventEmitter наследуется от Observable
outputToObservable
выглядит бесполезно2) Что такое
@UntilDestroy(),
откуда он импортирован?3) "В этом примере вместо обработки асинхронных подписок и ручного управления состоянием Signal обновляется синхронно при поступлении новых данных. Это делает компонент проще, понятнее и легче в обслуживании. "
Что стало проще? Что понятнее? можно было просто написать
<li *ngFor="let item of data$ | async">{{ item.name }}</li>
и не плодить лишний код.
4)
Какой смысл так писать? Что мешает вызывать сигнал напрямую в шаблоне? callstack короче будет и меньше лишнего кода.
5)
>
import { createSignal } from '@angular/core';
В angular 18 нет импорта по этому пути, есть "import {createSignal} from '@angular/core/primitives/signals';" но у него нет метода pipe.
google так же ничего не знает про метод pipe у сигналов.
Статья мусорная, содержит не рабочий код.
MaNaXname
Ну что Вы хотите. Джипити наше все!