The role of signals

When the Angular team announced signals as part of Angular 16, the developer community was immediately swept up in discussion and speculation. The excitement around this new reactive primitive led many to ask whether Angular was preparing to phase out RxJs entirely. But that is not where the framework is headed.

Below, I’ll look at why RxJs is here to stay and how it fits alongside signals.

What signals bring

Signals were introduced as a new reactive primitive in Angular 16, although the concept itself is not new — similar ideas already existed in frameworks like Solid.js. Developers familiar with that model will find signals intuitive. More details on signals can be found in this overview.

Signals are set to change how we structure code, how change detection works, and how performant applications can be. However, because signals are inherently synchronous, there are scenarios where their limitations become apparent — and those are precisely the situations where RxJs excels.

Comparing signals and RxJs

RxJs operates asynchronously, which allows multiple operations to run in parallel and independently. A practical example of this was presented by Maciej Wójcik at the wp.angular.love meetup.

Why Angular signals won’t replace RxJs — figure 1

In this snippet, the data variable may still be empty when it is assigned to someValue. That happens because the data is still being fetched and the subscription has not yet executed.

Signals, by contrast, are synchronous — they process operations in a deterministic order. This makes them ideal for managing state and rendering values directly in templates.

The case for keeping RxJs

Angular provides APIs to convert signals into observables and observables into signals. This bidirectional interop is a strong signal in itself: the framework expects developers to leverage both tools where each is strongest. Here’s an example of how that conversion works:

  step = signal('create');
  step$ = toObservable(this.step);
  ngOnInit() {
    this.step$.subscribe((step) => {
      console.log(step);
    });
  }


  users$ = of([{ id: 1, name: 'John Smith' }]);
  users = toSignal(this.users$);

While there are clear cases where signals outperform RxJs, the reverse is also true. Let’s explore scenarios where RxJs remains the better option.

First, RxJs handles events originating from multiple sources that require filtering and mapping more elegantly. Signals can manage such flows too, but the code quickly becomes harder to follow. The wide range of RxJs operators gives developers a substantial toolbox for such tasks.

A classic example where RxJs wins in terms of simplicity is listening to input changes combined with the debounceTime operator. The RxJs-only version looks like this:

 this.control.valueChanges.pipe(
      debounceTime(300),
    ).subscribe(value => {
      this.search.emit(value)
    })

The signals-only equivalent might look like this:

@Component({
selector: 'app-root',
template: `
<input [ngModel]="searchValue()" (ngModelChange)="searchValue.set($event)">
`,
styleUrls: ['./app.component.scss']
})
export class AppComponent {
searchValue = signal('');
debouncedSearchValue = signal(this.searchValue());


constructor() {
let timeoutId: ReturnType<typeof setTimeout> | undefined;
effect((onCleanup) => {
const search = this.searchValue();


timeoutId = setTimeout(() => {
this.debouncedSearchValue.set(search);
}, 500);


onCleanup(() => {
clearTimeout(timeoutId);
});
});


effect(() => {
console.log(this.debouncedSearchValue());
});
}
}

As shown, the RxJs version is often more concise and easier to read. That said, both approaches can solve the problem — and combining signals with RxJs often yields the best outcome.

Handling asynchronous events with signals alone is genuinely difficult. Moreover, RxJs has been around for years, is battle-tested across countless production applications, and is widely trusted by the community.

Michael Hladky has commented on Twitter about the idea of porting every RxJs operator to work with signals, pointing out that such a rewrite would not be an efficient use of effort.

Why Angular signals won’t replace RxJs — figure 2

Network requests offer another strong example of RxJs's value. XHR calls work asynchronously, and observables are naturally well-suited to managing them — including handling success, error, and completion states. With operators like switchMap or catchError, you can cancel or retry requests, something signals cannot achieve directly. Signals lack the established operator set RxJs offers for such cases.

Finally, Angular’s commitment to backward compatibility is a core part of its identity. Removing RxJs would force developers to undertake massive refactoring efforts — in some projects, essentially rewriting everything from scratch.

Wrapping up

Signals represent a significant step forward in terms of performance and the mental model of Angular development. Even so, RxJs remains a powerful ally when it comes to handling asynchronous events with confidence and flexibility.

In my view, signals will not fully replace RxJs. At most, they can take over the synchronous parts of what RxJs does today. The best approach is to combine both — using signals when state updates are straightforward and RxJs when dealing with complex async flows. That combination supports both application performance and developer experience.