major changes and benefits

WITH release Angular 18 the development team has expanded functionality RxJS Interopwhich greatly simplifies integration between Signals And RxJS Observablesoptimizing performance and improving code readability. In this article, we'll look at what RxJS Interop is and how it affects Angular development.

Evolution of RxJS Interop in Angular

RxJS Interop was first introduced in Angular 16 to bridge the gap between Signals And RxJS Observables. The first version allowed developers to convert Signals to Observables and vice versa. IN Angular 17 functionality has been improved to make transformations more efficient and provide better integration of RxJS operators with Signals.

Now, with Angular 18RxJS Interop takes things to the next level, offering better operator support, better transformations, and more flexible settings, making reactive state management even more convenient.

What is RxJS Interop?

RxJS Interop allows Angular developers to easily combine and convert Signals and Observables. Traditionally, Angular has made heavy use of RxJS to handle asynchronous operations such as HTTP requests or user events. With the introduction of Signals, Angular offers another way to manage reactive state.

RxJS Interop allows you to:

  • Convert Signals V Observables and vice versa.

  • Use RxJS operators such as map, filter And mergewith Signals.

  • Simplify working with reactive data.

  • Use method outputToObservable for direct transformations.

  • Apply flexible conversion settings.

  • Manage resource release using manualCleanup.

  • Set the initial value for Signals from Observables using initialValue.

This feature gives developers more control over reactive patterns while maintaining application maintainability.

A quick overview of RxJS in Angular

RxJS is at the core of Angular's reactive system, providing an Observable type for managing asynchronous data. It is used in many parts of Angular such as HttpClientforms and event processing.

  • Observable: A stream of data that generates many values ​​over time.

  • Subject: An Observable that broadcasts values ​​to multiple observers.

  • BehaviorSubject: Stores the last value and immediately sends it to new subscribers.

  • ReplaySubject: Repeats the buffer of previous values ​​for new subscribers.

These patterns are powerful, but can be complex when managing state or handling multiple asynchronous operations. Signals combined with RxJS Interop help simplify such scenarios.

RxJS Interop in Angular 18

Signals vs Observables: when to use what?

With Angular 18, developers get more flexibility in choosing between Signals and Observables:

  • Observables Ideal for continuous event data such as HTTP requests or form events.

  • Signals better suited to a reactive state with predictable data flows.

RxJS Interop makes integration between the two systems seamless, reducing boilerplate code and increasing code reuse.

Main features of RxJS Interop

Converting Signals to Observables

With RxJS Interop you can convert Signal to Observable and use RxJS operators like map or debounceTimeto transform the data. You can also use options such as requireSyncfor synchronous behavior:

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);
  }
}

Converting Observables to Signals with manualCleanup and initialValue

You can convert Observable to Signal, making state management easier, and use manualCleanup And initialValue for more control:

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();
  }
}

Using outputToObservable

outputToObservable allows you to convert the output of an Angular component to 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();
  }
}

Using RxJS Operators with Signals

RxJS operators can be applied directly to Signals, simplifying code, improving performance, and making debugging easier:

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

@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);
  }
}

Moving to synchronous reactivity with Signals

While RxJS Interop in Angular 18 provides powerful flexibility, it also opens the door to a new approach – moving away from asynchronous reactivity and towards synchronous reactivity with Signals. The ability to effectively use Signals reduces complexity by managing state synchronously, eliminating the need for boilerplate code associated with asynchrony.

Why consider synchronous reactivity?

The traditional approach using Observables and RxJS is great for handling complex asynchronous tasks such as HTTP requests or UI interactions. However, this approach often involves complex subscription management and potential memory leaks. Switching to Signals makes it easier to manage reactive state.

With Signals, data flow is easier to understand and updates happen synchronously, allowing developers to rely on immediate reflection of changes without having to manage asynchronous time frames. This shift is especially useful in scenarios with predictable and manageable data flows, which reduces the complexity of managing Observables.

Example: Replacing RxJS with Signals in a Firebase scenario

Let's look at an example similar to how you can manage interactions with Firebase. Traditionally, developers use RxJS to handle real-time updates and manage subscriptions, but with Signals we can replace this pattern with a simpler, synchronous approach.

Example of using synchronous 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: `
    <div>
      <h2>Items</h2>
      <ul>
        <li *ngFor="let item of data">{{ item.name }}</li>
      </ul>
    </div>
  `
})
export class AppComponent {
  private _dataSignal = createSignal<any[]>([]);
  data$!: Observable<any[]>;
  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();
  }
}

In this example, instead of handling asynchronous subscriptions and manually managing state, Signal is updated synchronously when new data arrives. This makes the component simpler, clearer and easier to maintain.

Conclusion

The new RxJS Interop in Angular provides a bridge to move between reactive paradigms and also opens the door to synchronous reactivity using Signals. By using Signals, you can simplify your application architecture, reduce the complexity of managing asynchronous threads, and create more predictable and maintainable code. For many state management scenarios, synchronous reactivity is more than enough and helps you focus on what matters most—creating a great user experience.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *