BLOG

Interpreting Signals Correctly – Angular Is Changing

Calendar Icon
December 13, 2023
10-minute read
Graphical representation of Wi-Fi analysis and network optimization, including signal testing, Wi-Fi coverage measurement, and network performance optimization.

Table of Contents

Over the past two years, the Angular team appears to have charted a new course. On October 8, 2021, the first discussion about standalone components was launched in the official GitHub repository in the form of an RFC (Request for Comments). The post was extremely comprehensive and detailed. The entire community was invited to actively participate in the development process of this new concept.

Participation was encouraging, and the feedback from both the community and the Angular team was consistently positive. Ultimately, the concept of standalone components was integrated into the framework with Angular 14. This marked the beginning of further discussions on topics such as the „Signal API,” „signal-based components,” and „control flow & deferred loading.” The community was also heavily involved in these discussions.

The RFCs did not introduce superficial changes, but rather fundamental ones, which were nonetheless quickly implemented into the framework by the Angular team. The Signal API was introduced with Angular 16, followed by an improved template syntax in Angular 17. Angular 18 is expected to complete the Signal API with signal-based components.

In light of these changes, our software development experts have taken a closer look at Angular. Below, we explain how these new features will impact the development of Angular applications in the future and how you can start preparing for them today.

Signal API

The typical Reactivity API consists of two main concepts: signals and effects. A signal contains a value that can change, while an effect reacts to changes in that value. For a detailed description of how this API is implemented in Angular, please refer to the official documentation: angular.dev. The goal of this article is not to explain the existing API, but to prepare advanced Angular developers for the future of the framework.

In a nutshell, Angular's Signal API is based on three fundamental elements:

  • signal generates a writable signal (WritableSignal).
  • computed generates a calculated signal (Signal).
  • effect creates an effect (EffectRef).

For developers familiar with other frameworks, the parallels to the corresponding concepts in Angular should be clearly evident:

  • Vue: ref, computed, watch/watchEffect
  • React: useState, useEffect
  • Svelte: $state, $-derived, $effect
  • @preact/signals: signal, computed, effect (here, even with the same labels)

Angular, therefore, does not reinvent the wheel, but rather adopts proven techniques from other frameworks.

RxJS

The introduction of the Signal API provides a clear alternative to RxJS, and Angular's long-term goal is to completely phase out RxJS as a required dependency. This offers numerous advantages. New users no longer have to grapple with the steep learning curve of this extensive library, as they can now implement everything using the tools built into Angular. Furthermore, potential issues associated with using RxJS—such as „glitches“ caused by inconsistent data and memory leaks from forgotten subscriptions—are eliminated.

Although RxJS remains indispensable in current development—especially since it is used in most libraries developed for Angular—the package provided by Angular @angular/core/rxjs-interop the ability to use RxJS observables in a Signal environment. This allows you to take full advantage of the new approach.

Signal-Based Components

Signal-based components were introduced to enable seamless integration of signals into Angular applications. By setting the parameter signals set to true in the @Component-Decorator enables this new functionality. What makes this unique is that signal-based components allow the use of member decorators such as @Input, @Output, @ViewChild ...eliminate. Instead, these are handled by appropriate functions such as input, output, viewChild etc. are replaced. All options, such as required, alias and transform in the @Input-Decorator is preserved in its equivalents.

Example of a signal-based component:

It is important to note that Angular processes decorators at compile time and that the JavaScript bundle does not actually contain any decorators. The new signal-based API follows the same approach. Angular processes input, output, viewChild etc., at compile time, to understand the application structure. This means that this API cannot be used throughout the code like traditional functions, but only works within the components.

Change Detection

Angular's change detection is based on either zone.js or requires manual use of the ChangeDetectorRef-Instance within a component. However, with the introduction of signal-based components, there is no longer a need to changeDetection-setting. Signal-based components allow Angular to automatically detect state changes as soon as the value of a signal used in the template changes. This automation effectively eliminates unnecessary re-rendering of component trees and leads to a significant improvement in performance, especially in large applications.

It is already possible to use signals within components and their templates. If the change-detection strategy is set to OnPush Once set, Angular responds seamlessly to any changes.

Input

In signal-based components, input parameters are passed as signals through the function input defined. These signals are read-only and always represent the current value that has been fed into the component from an external source.

zone-based

signal-based

A workaround is already available for using signals as input parameters. To do this, a getter and a setter are created for the @Input-Decorator is declared to return the corresponding signal and modify its value.

Output

The Class EventEmitter is a subclass of Subject from the RxJS library. The initial implementation of the output-The function should remain the same EventEmitter-Return an instance. Therefore, the use of the output parameters remains virtually unchanged.

zone-based

signal-based

Template

The signals are functions and must therefore be used accordingly in the templates. Angular does not automatically unpack them.

A current issue when working with signals in templates is that TypeScript’s type guard cannot guarantee the same return type for consecutive function calls. This makes it difficult, for example, to perform a nullability check on the signal value in the template in order to use it as a non-null value. An elegant solution to this problem has yet to be found. We therefore recommend including a reference to the signal value within a if-Create blocks and use this reference within the block's content.

Two-Way Binding

The traditional bidirectional binding between parent and child components requires some boilerplate code. Signal-based components, on the other hand, simplify this process considerably by introducing the model-function. This function generates a WritableSignal-An instance that automatically propagates changes to its value to the outside world.

zone-based

signal-based+

It is important to note that the „banana-in-a-box“ syntax does not work with signals. Until the Angular team provides an alternative, it is necessary to continue using the more verbose version for now.

zone-based

signal-based

Queries

The basic principle of queries is preserved in the signal-based components. The equivalents of @ViewChild and @ContentChild return their result in a signal. This eliminates the need to define a setter if additional control over dynamic queries is desired. The equivalents to @ViewChildren and @ContentChildren also return their result as a signal, using the complex class QueryList A simple array is used. This significantly reduces complexity and eliminates the need for additional observables.

zone-based

signal-based

A possible workaround for queries as signals could be implemented similarly to the approach used for input parameters. A setter for @ViewChild and @ContentChild can be called multiple times, whereas a setter for @ViewChildren and @ContentChildren is called only once, and any further changes are propagated via an Observable. It is also possible to omit a getter by ensuring that the created signal is always used in read-only mode.

Host Binding

The Angular team has not yet made a final decision on how to handle the two decorators @HostListener and @HostBinding should be used. There is currently no specification that mandates a signal-based alternative. At this time, it is recommended to temporarily use the host-Option of the @Directive– and @Component-Decorator.

Lifecycle Hooks

The signal-based components support eight lifecycle hooks. Due to their original dependence on zone.js With the introduction of signals and effects, these hooks will become obsolete in the signal-based components. The signal-based components will continue to use the methods ngOnInit and ngOnDestroy support, even though their use is optional. Initialization can effectively take place in the class constructor. The cleanup that must be performed when the instance is destroyed can be done either in the onDestroy-Callback of the injected DestroyRef-instance or in the onDispose-Callback within a effect-method. If you want additional control over the rendering process within a component, Angular has provided three hooks for this purpose since the current version (v17): afterNextRender, afterRender and afterRenderEffect, which are available as a Developer Preview.

Signals in Practice

To illustrate just how much these signals will simplify our lives, let’s look at an example. Our requirement is a component that expects a single Boolean input parameter. If this is „true,“ the component starts a timer that generates two random numbers every second and calculates their sum.

First, we'll implement the example in the traditional way using RxJS functionality.

Despite the task's low level of complexity, the component has become unnecessarily large and confusing. The @Input-Decorator requires a setter to set the subject's value. To prevent unnecessary calculations of the sum, a distinctUntilChanged-operator. Simply calculating the sum is not enough; in addition, auditTime necessary to avoid „glitches,” as well as share, to ensure that the observable is initialized only once. In the ngOnDestroy-In addition, all subscriptions must be cleaned up in the hook. There are many aspects to consider that aren't immediately intuitive.

Now let's implement the same component using a signal-based approach.

The code has become significantly shorter and clearer.

We can take it a step further and define a composable to make the logic within the component even easier to understand. Composables are already well-known and popular among developers familiar with other frameworks. They offer an elegant way to encapsulate logic in functions and reuse it in various places throughout the code. This helps create clearer, more modular, and more maintainable codebases.

We can define the code for setting up the timer as a separate function. This function should be as generic as possible and therefore expects the logic to be executed within the time interval as a callback.

We can now use this Composable in the component's constructor to make our code even more elegant.

The example presented illustrates how the introduction of the new Signal-based components accelerates application development and makes it more efficient. Furthermore, the Signal API—particularly through its composable approach—opens up new possibilities for numerous helpful libraries that will further enhance application development.

Conclusion

The introduction of signal-based components represents a significant milestone in the development of Angular and is emerging as the upcoming standard in the framework, comparable to the establishment of standalone components. The transition to this new approach may be a challenging task for many developers, as it requires a shift in mindset and familiarization with innovative concepts. It is essential to emphasize that the specification for signals and related concepts has not yet been finalized. Developers can track the current progress of signal-based components in the development branch of the Angular repository.

For anyone who doesn’t want to wait for official releases, various workarounds offer the opportunity to integrate the new concepts right now. When it comes to existing projects, the transition to signal-based components may seem challenging; however, using them in the development of new projects is already worth considering today. This approach allows developers to benefit from the advantages of the new features early on and to familiarize themselves with Angular’s future standards.

Sources

https://angular.dev/guide/signals
https://github.com/angular/angular/discussions
https://github.com/angular/angular/tree/signals

share ->

Related Articles

Home
Company