angularsignalszonejsangular16

Angular 16 Signal update doesn't update view


I am testing angular 16 signals and per my understanding, when I disable zone.js and call signal.update() the view should be updated with new value. It is not. Please help me to understand why.

main.ts

platformBrowserDynamic().bootstrapModule(AppModule, { ngZone: 'noop' })
    .catch(err => console.error(err));

app.component.ts

@Component({
    selector: 'app-root',
    template: '
        <h1>{{ title() }}</h1>
        <button (click)="click()">Change</button>
    ',
})
export class AppComponent {
    title = signal('Hello');

    click(): void {
        this.title.update((value) => value + "!!");
    }
}

I am expecting that after button click, value of 'title' will be updated from 'Hello' to 'Hello!!'. It is not updated.


Solution

  • TL;DR: Because Angular still uses zone.js in order to trigger change detection.

    Zone.js behind the scenes is wrapping a bunch of browser APIs in order to detect when an event happens on the page (there are specific types of events it looks for, see the Angular docs about zones for more details). It doesn't notify Angular what event exactly happened, but it will say that it is possible that some value somewhere was changed. Angular then kicks off change detection that loops through the whole component tree, checking all the components for changes. If ChangeDetectionStrategy.OnPush is enabled, then it can be more efficient, checking only the components in the tree that may have changed.

    This is why you see your function execute to update the signal correctly. The missing link is that zone.js would detect that and notify Angular that something was updated in order to kick off the change detection. If you were to add a call to manually trigger change detection, then it should get your example working again.

    Signals are only the first step on the path to being able to remove zone.js completely from a compatible app. There is a (now complete) RFC on Angular's GitHub repository discussing Signal-Based Components, which would be a further step towards being able to accomplish a zoneless Angular app. It will just take some time for all of the infrastructure of Angular to be changed, since zone.js is such a fundamental part of how Angular currently works.

    Here is a slightly-old article that discusses more of zone.js and its role in the Angular change detection process (it is still pretty relevant).

    Update (2024-04-15): This answer is also supported by content from the 2024 NG-Conf Keynote. They talk about the new signals APIs, that are another big step in this direction (specifically, signal inputs, signal queries and model inputs, and the new output API). These APIs are part of what I mentioned as far as Signal-Based components before. When they finished talking about that, they have this to say about removing Zone JS:

    Well, we still have some improvements we want to make before we can start recommending that people go fully zoneless. Using the new signal API is a big leap forward in getting ready for getting zones out of your application and you know has other benefits as well, but there's still some work we want to do to make sure that the experience is as streamlined as we'd like. In particular, how the framework schedules tasks, and how you'd interact with change detection inside your tests.

    But we are making significant progress towards that point. We'll have a well-lit path for fully driving UI updates from signals.

    Here is a link to the relevant part of the video.

    Update (2024-06-04): As of Angular version 18, they have introduced experimental zoneless change detection. It is not production ready, but it is something you can try out. You can see some details in the announcement blog post. If your app uses signals everywhere (and especially if it uses OnPush change detection), it should work fine without Zone.js.