Tech Blog

Angular vs Signals

Paradigm shift or the nth opt-in feature?

Filippo Pellegrini
Front-End Developer
5 minutes read
Angular, Signals, RxJs, Observables, reactive programming, and design patterns
This article is also available in Italiano 🇮🇹

Recently, a discussion resumed among frontend developers, especially those in the JavaScript world, about a primitive that will soon be introduced in some of the most famous frameworks like Angular and React: signals. This debate has historical roots and focuses on the use of signals as a way to handle events and application state more efficiently. It is therefore being evaluated whether the adoption of signals represents the addition of a useful feature for developers, or just another opt-in that, in the hands of inexperienced users, could create difficult-to-scale applications full of spaghetti code. Whether developers like it or not, signals are coming and gaining more and more popularity thanks to a clear and simple syntax and a predisposition for reactivity.

A short history recap

Despite the popularity gained in recent months, signals are a concept that originated in the early days of computing. Unix systems are still using them to send notifications between processes efficiently. Signals gained traction thanks to early-research on reactive programming, and have since been implemented by beloved declarative JavaScript frameworks, often with different names and logics, to allow developers to manage the application state and ensure precise change detection and rendering, therefore increasing efficiency. The pioneer in implementing signals in a modern context is undoubtedly Solid.js, which uses signals for reactive programming development, allowing fine-grained reactivity.

What are Signals?

Solid.js defines signals as:

"Signals are the cornerstone of reactivity in Solid. They contain values that change over time; when you change a signal's value, it automatically updates anything that uses it."

Signals are an event-driven programming paradigm, similar to that of the DOM, which allows developers to generate custom events and create effects in the code upon the emission of a value. Signals offers numerous advantages, including writing simple, reactive, and easy-to-maintain code. Additionally, signals are agnostic with respect to the framework used, which means they integrate starting from a vanilla JS library with ease. Signals can be created from a keyword like signal in Angular:

export class MyClass {
    count = signal(0);
    double = computed(() => this.count() * 2);
    
    changeCount() {
        this.count.set(5);
    }
}

In this example, a signal of type number is created with an initial value of 0, while a second signal is calculated from the first one. When the changeCount function executes, count will be updated with the value of 5, while double will become 10 thanks to the computed method that returns a new signal based on another one's value. Furthermore, other methods are made available to the developer in the getter, used for modifying and notifying them to observers, such as mutate, update, and effect:

const count = signal(0);

count.set(2);
count.update(count => count + 1);

The update method allows modifying the signal based on its previous value, which is useful in the case of immutable starting values.

const ids = signal<Id[]>([]);

ids.mutate(id => {
    id.push({name: 'Mario Rossi', dateOfBirth: '11/11/2000'});
});

The mutate method, similarly to update, allows modifying the signal value by directly updating it.

const count = signal(0);
effect(() => console.log('Count is:', count()));
//LOG: Count is: 0

count.set(1);
//LOG: Count is: 1

Finally, a signal can generate effects thanks to the effect() function. This function will execute the code inside it every time the signal emits a value. It should be specified that the implementation shown here refers to the preview of Angular version 16, and depending on the framework used or future changes, it could vary drastically. In the case of the new Signals' API in Angular, helper functions for integration with RxJs will be added, but they are not yet defined.

Angular's current status

The majority of the Angular community is excited about the implementation of signals, considering them a useful and necessary upgrade. In the last three releases, the development team has introduced numerous features, such as standalone components, improved stack tracing, and the addition of directives to host elements, which have modernized and simplified the framework. With these improvements, development is much easier and faster, making the learning curve less steep for newcomers. However, to fully understand the usefulness of signals, it is necessary to understand how Angular renders components and how it detects changes.

The problem with Zone.js and Change Detection

In Angular, the Change Detection process keeps the interface synchronized with the internal state of the application. It allows the framework to automatically update the DOM when the application state changes. Angular uses a library called Zone.js to detect changes. This library checks the application's data model objects to determine if their values have changed. However, the use of Zone.js can be a performance issue for Angular, as it must be loaded and executed before it can detect changes in the data. Additionally, every event must be tracked even if it does not cause changes in the data model, and since Angular does not use a virtual DOM, the entire component tree is checked by dirty checking, impacting speed and efficiency.

Image 1 - Zone.js (zone.js at work)
Image 1 - Zone.js (zone.js at work)

Can we fix Change Detection?

Currently, the problem generated by Change Detection in Angular cannot be definitively solved. However, developers use an alternative that is easily integrable with reactive programming (which I recommend reading about in this fantastic article written by colleagues Cristian Bianco and Mauro Celani, which you can find here). This method is based on the OnPush strategy, which allows changes to be detected only at the moment when the data is updated, i.e. on the "push". In particular, they use the async pipe in templates, avoiding subscribing in the code. Instead of navigating the entire component tree, Angular will only check the subtree from which the modification originated.

@Component({
  selector: 'async-observable-pipe',
  template: '<div><code>observable|async</code>: Time: {{ time | async }}</div>'
})

export class AsyncObservablePipeComponent {
  time = new Observable<string>((observer: Observer<string>) => {
    setInterval(() => observer.next(new Date().toString()), 1000);
  });
}

The key to fine-grained reactivity

The listed issues will be resolved by the introduction of signals in Angular. In particular, the team aims to add a reactive primitive that, integrated with the Angular templating engine, allows notifying the framework when the value associated with the view changes and needs to be updated in the DOM. This process of fine-grained view updating, also known as fine-grained reactivity, will eliminate the need to use Zone.js. Another advantage of using signals is their glitch-free execution. They are based on two abstractions: the Producer and the Consumer. A dependency tree is built, where each node has two edges that connect it to its child or parent nodes. This allows each node to know the state of its consumers and vice versa. In this way, a Producer can update all its Consumers and propagate changes without emitting side-effects, thus avoiding creating intermediate states that the developer would need to manage to prevent glitches. When all nodes have been updated (synchronously), the effects are executed, which solves a problem often encountered when using Rxjs.

The future of RxJS

One of the most frequently asked questions regarding the introduction of signals in Angular is: Will RxJs be eliminated? The answer is definitely no. Despite signals being a useful tool that will improve developers' experience, they cannot replace RxJs and its observables in all asynchronous operations, such as http requests. The relationship between signals and RxJs will likely be symbiotic, allowing for the use of signals for handling more immediate values and the use of observables for all asynchronous operations that can benefit from its advanced operators. In this way, both tools can coexist and integrate effectively, improving the efficiency and scalability of Angular applications.

Image 2 - Synchronus and Asynchronous reactivity (Synchronus and Asynchronous reactivity)
Image 2 - Synchronus and Asynchronous reactivity (Synchronus and Asynchronous reactivity)

Final toughts

In conclusion, the introduction of signals in Angular represents a significant evolution that will greatly improve developers' experience and provide important performance boosts, further consolidating Angular's position as one of the leading Javascript frameworks on the market. The combination of signals with RxJs will also offer a highly efficient and scalable solution for handling both synchronous and asynchronous operations, allowing developers to create even more powerful and complex applications.

written by
Filippo Pellegrini
Front-End Developer
He works as a front-end developer at SMC and has a passion for JavaScript frameworks, particularly Angular, where he employs reified reactive programming. He also enjoys experimenting with CSS. He has a bachelor's degree in computer science from the University of Perugia.

You might also like…