Angular 17: Signals, Server-side rendering, and Standalone

We’re not done with Angular 17 updates! Here are a few more updates on Signals, Standalone, and Server-side rendering.

Signals

Signals have graduated from developer preview and are now officially part of the framework, except the effect function that is still being tweaked by the Angular team. A big step for Angular Signals.

Standalone

With Angular 17, a new Angular app is generated with standalone components by default. You can upgrade your Angular CLI to test that out, and if you don’t want to use standalone by default and your new apps, this feature can be turned off in angular.json with the following config:

   "projects": {
    "my-app": {
      "schematics": {
        "@schematics/angular:component": {
          "standalone": false
        }
     Code language: JavaScript (javascript)

Server-side rendering

With Angular 17, you can create an app that uses server-side rendering with a single command:

ng new --ssr

If you want to add server-side rendering to an existing app, you can run:

ng add @angular/ssr

It’s also possible to pre-render an Angular app using:

ng build --prerender

That way, you don’t need Node.js hosting and can use static, pre-rendered HTML files. I’ll explore this more in the newsletter before the end of the year.

On that same topic of server-side rendering, hydration graduated developer preview, which means an app rendered on the server side won’t rebuild its entire DOM in the browser. Instead, Angular will take over that DOM and re-hydrate it with up-to-date data as needed.

Async pipe, Signals, and Change Detection

Yesterday, we covered what markForCheck() can do in terms of change detection and why it could be needed at times.

Regarding best practices, I often mention using the async pipe or Signals to improve change detection and performance in our apps. Why is that? That’s because both the async pipe and Signals have some extra code to help Angular detect changes.

Here’s what we can find in the source code of the async pipe:

The last line of code indicates that whenever an async pipe receives a new value, it marks the corresponding view for check, triggering the need for change detection. That’s the reason why the async pipe works well even when used in an OnPush component: Its internals override the behavior of whatever change detection strategy we use.

Signals do something similar by marking their consumers (or “subscribers,” in a sense) as dirty when the Signal value changes:

As a result, and back to the point I was making yesterday, Instead of trying to use markForCheck() directly in our code, we should rely on the tools designed to do it automatically and in an optimized fashion for us.

Conclusion: Always use the async pipe with RxJs (you can always do so) or use Signals for an even better experience.

Perf and template syntax – Example 3

Yesterday, we looked at the pros and cons of our second code example.

Today, let’s cover the pros and cons of our third and final example:

Example #3 – Signal

<div *ngFor="let data of dataSignal()">{{data.name}}</div>

This last example is very interesting for a few reasons. First, if you follow Angular best practices, you might be screaming that we’re calling a method in a template, and you would discard that code immediately.

But the thing is, this is a Signal, and Signals are different. It’s perfectly fine to call a Signal in a template, and as a matter of fact, this is the way to do it so Angular knows where Signals are read and can then optimize DOM updates based on that information.

Even better, when we use a signal with ngFor, the subscriber to that signal is actually a view (an instance of ng-template) and not the entire component. When Signal-based components become part of the framework, Angular will be able to re-render just that view instead of the entire HTML template of that component.

What are the cons, then?

The only downside of Signals is that they require Angular v16+, so you’ll need to upgrade your apps to use them. The other downside is that the full benefits of improved change detection are not there yet and won’t land in v17 either, but the Angular team is making steady progress, and some early parts of Signal-based components were released in version 16.2.

Other than that, Signals are the way to go. if you’re just getting started with Angular and building a new app, I believe you should use Signals as much as possible.

RxJs and Signals interoperability

Angular 16 introduced several features related to the brand-new Angular Signals, which include two functions enabling RxJs interoperability.

toObservable()

The name says it all. toObservable() takes a Signal and returns its data as an Observable:

This new Observable gets updated every time the underlying Signal value is updated. We can subscribe to it using the async pipe, for instance (demo code here):

toSignal()

toSignal() does the opposite of toObservable(). It turns an Observable into a Signal. Since Signals are different from Observables (a Signal always has a value right from the start – an Observable does not), the default behavior of toObservable() is to return a Signal that supports undefined as a default value:

This can be changed by providing an initial value as follows:

Signals: effect()

After talking about how to create Signals, how to update them, and how to derive a Signal value from other Signals, let’s look at how we can register side effects out of any number of Signals.

Enter effect(). The behavior of effect() is almost the same as computed(), with one major difference: computed() returns a new Signal, whereas effect() doesn’t return anything.

As a result, effect() is suitable for debugging, logging information, or running some code that doesn’t need to update another Signal:

You can see that code in action here on Stackblitz.

Note that trying to update a Signal within an effect() is not allowed by default, as it could trigger unwanted behavior (infinite loop such as SignalA triggers update of SignalB that updates SignalC that updates SignalA – and around we go).

That said, if you know what you’re doing and are 100% sure that you won’t trigger an infinite loop, you can override that setting with the optional parameter allowSignalWrites as follows:

Signals: computed()

After introducing how to create Signals and how to update them, let’s take a look at one more exciting feature that helps replace the need for RxJs Observables.

How to emit a new Signal value when one or more Signals get updated? That’s what computed() does. In my Signals course, I illustrate computed() with the following example:

In the above code, this.rates() and this.currency() are two different Signals. this.rates() emits up-to-date exchange rates for all currencies in the world. this.currency() emits the current currency selected by the user.

computed() takes a function as a parameter. The function returns the computed value from my two Signals; in this case, the up-to-date exchange rate for the current currency. If the exchange rates or the currency get updated, this computed Signal will emit an updated value automatically.

This is somewhat similar to combining several Observables and using switchMap or combineLatest to get a customized result. It’s a lot easier with Signals (one line of code!).

Two ways to update Angular Signals

Yesterday, I wrote about some best practices around exposing a Signal in our Angular applications. Let’s now take a look at the two different ways a Signal can be updated.

set()

The easiest way to update a Signal is the set() method. Nice and easy for basic data types such as strings or booleans:

update()

When the new value of a Signal depends on its previous value, update() is the best method to use. This is the ideal method for a counter, for instance:

Here is how to increment the counter based on its current value:

TL;DR

  • When you need to update a simple value (string, number, boolean), use set().
  • If that new value is based on the previous one, use update() instead of set()

Signals: Why and when do we need them?

With the release of Angular 16, Signals are now available as a developer preview, which means we can start learning about Signals, testing them, and possibly adopting them.

The end goal of Signals is simple: Improve change detection in the Angular framework by removing the need for Zone.js. A signal-based application will be able to update individual views (a view is a sub-set of a component template — we create new views every time we use structural directives such as ngIf or ngFor) one by one instead of checking the entire component tree, enabling laser-focused updates of our DOM.

A secondary goal is to make Angular easier to learn by making RxJs less critical than before, which also applies to state management libraries such as NgRx or NgXs. In other words, we can rely less on operators, subjects, and the like.

Now that we covered the why, let’s talk about when to use signals. To get the full benefit of Signals in the long run, using them everywhere we have Observables or data bindings makes sense. I know this sounds like a lot of work, but that’s how we can get to a point where Angular can tell exactly which components must be updated when a value changes, no matter their location in the DOM tree.

An upcoming feature of Angular (v17 or later) will be signal-based components, where inputs, outputs, and even two-way bindings will be expressed as Signals:

I believe it makes sense to start using Signals as soon as possible. The base API is available in Angular 16 and is easy to learn. I already published a course that will be continuously updated as Signals evolve.

If you have any specific questions or concerns, feel free to send me your questions. I’ll be covering more about Signals as part of this newsletter.

Angular Signals are in the works!

This is probably the most significant change coming to the framework since the release of Angular v2 many years ago.

A new feature called Signals is being prototyped in Angular, and we can follow the entire discussion on GitHub.

Where do signals come from?

They are the answer from the Angular team to several different requests from the developer community over the years, more specifically:

  • Being able to use Observables with @Input
  • Being able to use Angular without Zone.js to have more control over change detection.
  • Having state management built-in with the framework so we don’t need libraries like NgRx or NgXs anymore.
  • Being able to use Angular in a reactive way without relying on RxJs.

In other words, Signals will be a clear and unified model for how data flows through an application and will work without any dependency (no RxJs, Zone, or other third-party library needed).

Will we have to change everything in our apps?

No, because the Angular team has communicated that:

  • Signals will be optional, and the current way of working with Angular will remain in place.
  • Signals will provide ways to play nicely with RxJs (a Signal can be turned into an Observable and vice-versa)

What will signals look like?

It’s still early to have a definite API. We’re not 100% sure what signals will look like, but the early prototype API looks like this:

Creating a signal with a default value (of 0 in this example)

The above line of code would be very much like doing const counter = new BehaviorSubject(0);

Setting a new value to a signal

Updating a value derived from the current value

Updating a value within a signal (mutation of the internal state)

Displaying the value of a signal in an HTML template or Typescript code

The above would be the equivalent of counter | async when using RxJs. The nice thing is that with Signals, there will be no more .subscribe() and .unsubscribe().

If you want to dive into more details, here is the documentation of the current prototype. Again, this is still early and I would not expect Signals to become fully available before Angular 17 or 18, but this is very exciting nevertheless!