DEV Community

Cover image for Angular & signals. Everything you need to know.

Angular & signals. Everything you need to know.

Robin Goetz on March 02, 2023

On February 15th, the Angular team opened the first PR that introduces signals to the framework. This early PR has gained huge momentum and sent th...
Collapse
 
ivanivanyuk1993 profile image
Ivan Ivanyuk • Edited

Added comment github.com/angular/angular/pull/49...

If we don't preprocess function in computed at compile-time or use some form of reflection to atomically notify only affected signals, won't it require O(n) change checks, where n is a quantity of signals in application, effectively making it have same algorithmic complexity as ChangeDetectionStrategy.Default and making it harmful for angular ecosystem?(with signals there will be 2 ways to run change detection in O(n), using signals or ChangeDetectionStrategy.Default(which is simpler to do than signals), and 1 way to run change detection in ~O(1) - using rxjs and async pipe)

Collapse
 
elpddev profile image
Eyal Lapid • Edited

I've followed the link. Great resource, thank you!

I was wondering, why say O(n) of all signals of the all application. Reading the code, it seems:

  1. the consumer register globally to notify it wanting signal registration,
  2. Then the consumer activates the computed callback function,
  3. the signals look for in that global registration the registered consumer and subscribe that consumer only
  4. when the callback is done, the consumer de-register from the global service to stop other signals activation later in other logics to subscribe it to them.

It seems that the consumer is collecting only the signals detected while executing the callback.

github.com/angular/angular/pull/49...

  signal(): T {
    producerAccessed(this);
    return this.value;
  }
Enter fullscreen mode Exit fullscreen mode

github.com/alxhub/angular/blob/119...

export function producerAccessed(producer: Producer): void {
  if (activeConsumer === null) {
    return;
  }

  // Either create or update the dependency `Edge` in both directions.
  let edge = activeConsumer.producers.get(producer.id);
Enter fullscreen mode Exit fullscreen mode

github.com/alxhub/angular/blob/119...

  private recomputeValue(): void {
   // ...

      const prevConsumer = setActiveConsumer(this);
    let newValue: T;
    try {
      newValue = this.computation();
    } catch (err) {
      newValue = ERRORED;
      this.error = err;
    } finally {
      setActiveConsumer(prevConsumer);
    }
Enter fullscreen mode Exit fullscreen mode

github.com/alxhub/angular/blob/119...

export function setActiveConsumer(consumer: Consumer|null): Consumer|null {
  const prevConsumer = activeConsumer;
  activeConsumer = consumer;
  return prevConsumer;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ivanivanyuk1993 profile image
Ivan Ivanyuk

Oh, thanks, now I understand. We know for sure who is current consumer(because we track it in activeConsumer) and current producer(we can track it in code), so we have all the information needed to create dependency graph which will run atomically in O(1)

Collapse
 
goetzrobin profile image
Robin Goetz

Looks like I am playing catch-up here :D Thank you @elpddev! I had the same question and even better that you pointed to the direct lines in the code that outline the process!

I want to (again) point to @michael_hladky 's amazing talk at the NG - BE conference where he explains this exact mechanism! You can check it out here.

He also shows how we can leverage this built in reactivity of signals for fine grained updates to the DOM, not only on view level, but (with the help of structural directives) even on binding level!

Even better, he compares this mechanism with the RxJs & async pipe approach you are mentioning!

Please check it out and let me know if it addresses the concerns you mention in your comment!

Thread Thread
 
elpddev profile image
Eyal Lapid

Great video! He does goes into the details of the mechanism. The pipes design were very informative. It has a been a while that I actually dealt with Angular projects code.

It reminded me I encountered this kind of pattern years ago doing a project in Meteor in 2017 I think.

docs.meteor.com/api/tracker.html#T...

Tracker.autorun(() => {
  const oldest = _.max(Monkeys.find().fetch(), (monkey) => {
    return monkey.age;
  });

  if (oldest) {
    Session.set('oldest', oldest.name);
  }
});
Enter fullscreen mode Exit fullscreen mode

Going back to it looking at the code it seems the same pattern.

github.com/meteor/meteor/blob/77df...

  _compute() {
    this.invalidated = false;

    var previousInCompute = inCompute;
    inCompute = true;
    try {
      Tracker.withComputation(this, () => {
        withNoYieldsAllowed(this._func)(this);
      });
    } finally {
      inCompute = previousInCompute;
    }
  }
Enter fullscreen mode Exit fullscreen mode

Funny, it seems to be still alive somewhat. They even have a recent PR merge it seems for partial async support.

github.com/meteor/meteor/pull/12294

Collapse
 
santoshyadavdev profile image
Santosh Yadav

Great article, looking forward to see more articles from you.

Collapse
 
goetzrobin profile image
Robin Goetz

Can’t express how much this means to me Santosh! You and Lars’ community is the reason I started writing in the first place! Incredibly grateful for your positive impact on the Angular community and excited to be part of it!

All I can say is: Dankeschön mein Freund!

Collapse
 
santoshyadavdev profile image
Santosh Yadav

Danke Robin, I am happy to see your growth. Happy that we are able to build a great community, pass it forward brother, help more developers in their journey.

Collapse
 
andriyonoshko profile image
Andriy

This is a great article that was worth every minute spent reading it. Thank you for providing an in-depth analysis of all sides of the current state of change detection, as well as a detailed description of the Signal concept and mechanism.

Collapse
 
goetzrobin profile image
Robin Goetz

Thank you! I appreciate you taking the time reading it and am glad that you liked it!

Collapse
 
cyberdyme profile image
cyberdyme

If you take an existing application upgrade to version 16 or later when they are out. What work will be required to get the application working without zones and signals; are we talking significant changes or not or will they co-exist while you change the application component by component.

Collapse
 
goetzrobin profile image
Robin Goetz • Edited

Very good question! There will definitely be a way for signal and zone based change detection to co exist in the same application.

@eneajaho shared a tweet about a pull request introducing scoped zones, which means that we would be able to decide the way we want to run change detection on component level.
The tweet is here:

The conversation in the comments is also really interesting!

Let me know if that helps!

Collapse
 
fjorlin profile image
Marvin Tobisch

Amazing summary that really helped me understand how Angular Signals will work under the hood.

I guess what I'm left wondering about is the end goal the Angular team is chasing with this. By all accounts, it seems to be replacing Zone.js. But while the performance-benefits of Signals are undeniable, using them does effectively add boilerplate code (at the very least having to explicitly create a signal, then use .set() to change its value) compared to the magic that was Zone.js.

So in the end, Angular will have Signals that have a bit of learning attached to them and RxJS that has a lot more learning attached to it. Compared to before, where you had RxJS, but at least all other change detection happened automagically.

Not saying I disagree with the elegance that is Signals, but I doubt this change will do much to attract new devs to Angular that already found it too much of a bother to learn.

Collapse
 
goetzrobin profile image
Robin Goetz

Replacing zone based change detection is absolutely one of the main goals of this effort. Performance is one side of the coin, but the other side is that there are so many ways to make easy mistakes with the zone, especially if you are unfamiliar with how the "magic" actually works. With signals there is one simple mode to update the view: If you change the signal, the view will update.

So I would almost argue that the additional boilerplate is a feature, not a bug. Which values trigger a view update is now explicitly stated, directly in the code. Also, since we exactly know which values' changes trigger a view update, all the gotcha's that come with ZoneJs will disappear. Ultimately, developers will be able to care much less about how the framework detects changes and be able to fall back on one simple model: If its a signal, a change to its value will update the view.

Also, signals are not bound to just be used in a component. There is a great article by @ryansolid that lays out the benefits of using signals.

The example of how easy it is in SolidJs to turn local state into global state when using signals applies just as much to Angular. It does not matter if a signal is declared in a component or a service that is injected by Angular's incredibly dependency injection mechanism, it will work the same way.

To your point with RxJs. I agree that RxJs has a big learning curve and is something a lot of developers new to Angular struggle with. However, I believe that signals will actually help new Angular developers ease into RxJs. Signals will introduce new developers to reactivity and declarative code. They also solve a lot of issues that we currently (ab)use RxJs for better and easier. I am thinking of the example in the article of trying to declaratively derive state from 2 inputs. Ultimately, I think we will end up with an ecosystem that will still be integrated with RxJs, but this integration will use RxJs much better and only for the thing it is really good at: Describing the complete asynchronous behavior of each feature in its declaration.

I think that is definitely a valid feeling and I cannot say that signals alone will attract new Angular developers. I do want to point out that there is a lot more improvements coming to Angular and that the introduction of standalone components (making modules optional) was a huge improvement already. I think the most important thing to note for me here is that the Angular team seems to be very much aware of the initial learning curve of the framework and is working to make it easier for new developers to get started.

Finally, I think with the trajectory the framework is on it will also become "more worth it." A lot of the current issues with the framework are actively being worked on. Also, there are amazing community projects such as AnalogJS or Angular THREE that continue to make Angular even more powerful.

Ultimately, only time will tell how much signals will impact the popularity of the framework. However, as someone already working with Angular I couldn't be more excited. This will be an amazing new tool provided to all of us and I cannot wait to see what people will build with it!

Collapse
 
fjorlin profile image
Marvin Tobisch

Thank you for your detailed and thoughtful response!

Regarding the first point, it is true that Signals are going to make Angular more "transparent" to work with. As an Angular developer that only later learned React, this is something that I found strangely appealing about explicitly having to call setState/setVar as well. You are in direct control of how often the view updates and performance seemed at least implicitly much more efficient because of it. Compare that to Angular where just running console.log() in ngDoCheck() to see how often change detection runs for no reason can give you shivers.

But from what I understand about Signals, there are some new pitfalls that devs might have to learn.

Here is one example from Vue I found interesting. In order for Signals to be picked up as dependencies by a consumer, they have to be read at least once on the inital render/execution of the consumer. And if they're not (due to if/switch expressions, also async constructs (?)), the consumers won't update when the Signal changes. It won't be a problem if everything in a consumer is also strictly using Signals, but if you mix and match with normal variables, you might run into confusing problems. Or am I getting this wrong?

Additionally, and this is more of a personal preference, but I really liked the explicit .subscribe() call that you would for example use with RxJs observables. With Signals, from what I gather, the only way to "subscribe" to changes is to use "effect()". And the only way that effect runs is if you explicitly use the Signal inside it to register it as a dependency. But what if I don't care about the new value, but only care that it has changed? I would have to artificially read the Signal, perhaps at the start of the effect to register it as a dependency, do the actual logic afterwards. Not a dealbreaker I guess, but it doesn't seem elegant.

As someone who only learnt of Signals yesterday, something that is becoming apparent even to me however, is that a lot of the major frontend frameworks are moving closer together with this. Vue has had a form of Signals for a long time, React's Hooks also very similar, then there's obvious SolidJs and now Angular, all using the same reactive concept with very similar syntax as well. Feels like that is a good thing.

Thread Thread
 
goetzrobin profile image
Robin Goetz

I agree! There will definitely be new pitfalls and edge cases that we all run into. I think mixing reactive and non reactive variables in a reactive context is one of those things. Also, your point of subscribing to changes without needing the value is definitely valid. I know that SolidJs support an on function that allows you to explicitly declare dependencies, maybe that is something the Angular team will add too.

I would need to double check the Github discussion to see if this use case was already mentioned. If not, I would invite you to definitely bring this up and see what the Angular teams thinks about this! I would be excited to see their response!

Agreed! If you watched the stream with Ryan, Alex, and Pawel you could see their excitement for this "new" pattern that seems to address one of the core issues all frontend frameworks are dealing with: Keeping application state in sync with the user interface.

Thread Thread
 
elpddev profile image
Eyal Lapid • Edited

I was also thinking how signal architecture behave with conditional statements. I will be glad if someone could clarify this and how signals actually work. How the subscription mechanism works.

If I understand correctly, the only way a consumer to subscribe to a producer without doing "subscribe" explicitly like in Rxjs, is if they communicate in other ways:

  1. Through an implicit injection like "this" which wont work on anonymous functions
  2. Or through a global singleton context/service like an imported javascript module.

So the consumer has to do a subscription phase:

  1. It has to register its intention on subscribing with the global service,
  2. activating the callback,
  3. the signals primitives in the callback that are activated pick up the consumers from the global service and notify them to subscribe to them.
  4. The callback ends and the consumer de-register itself from the global service for wanting to be notified of new primitive activation.

I this is wrong, I will be happy to be corrected and learn how signals works in real.

But if this is right, then the subscribing is done only on the primitive that were actually had been activated. If those primitives where in a conditional statement (ex if/else) that was not entered, if that primitive at a later time change its value, the consumer will never know about it.

Is there an example of if/else inside a "compute" signal that use other signals inside the if/else? Or loops that exist conditionally?

This also is true for async/await as the callback must be synchronize, otherwise the registration phase must also be asynchronized, which means it will pick signals primitives existance notifications from other logic in the app that run in the meantime which the consumer did not intent to register to, all because they use the same global service to register/notify of existence.

Signals registration if I understand all this is rely on the fact that javascript is single threaded and the callback in sync so no other parties can notify the global service while the callback is running.

So I am not sure signals could be used in any other way then a simple synchronize computations that do not involve conditions also. Ex:

const ferengi_happiness = compute(() => products_sells * price);
Enter fullscreen mode Exit fullscreen mode

Is this correct? Or the mechanism is totally different?

** edit 1 **

Regarding the if/else conditions block, I've re-read @fjorlin comment. If I understand, so if the desire is to be reactive on the logic inside the "if" statement, than the condition of the "if" statement needs to be reactive also so the variables participating in the conditions needs to be signals also. Then when they change the computation is reactivated, the if section is entered, and the signals within it are picked up.

Thread Thread
 
goetzrobin profile image
Robin Goetz

You are correct on the if/else conditions. For signals to be conditionally added and removed as triggers of the execution context, the condition must be reactive (one might call it signal driven) also. Only then does the automatic subscribe/unsubscribe logic that drives signals under the hood kick in.

This talk explains in more detail how the registration process works. It lays out the overall concept and gives some good examples, which includes computed and effect. Let me know if this clarifies some of your questions or if you'd want me to go into more details of the subscribe/unsubscribe mechanism!

Collapse
 
joelbonetr profile image
JoelBonetR 🥇

Top post!

Do Angular already implements signals on the stable version?

Didn't used it since v6, it may be worth to take a try at this 😁

Collapse
 
goetzrobin profile image
Robin Goetz

Signals will be officially part of the v16 release later this year.

There is a first release candidate that includes an early prototype for developers to get started: github.com/angular/angular/release...

There’s also some great resources by the community. My go to is this stackblitz by Enea Jahollari: stackblitz.com/edit/angular-ednkcj...

Hope this helps!

Collapse
 
joelbonetr profile image
JoelBonetR 🥇

Thank you very much Robin!

Collapse
 
spock123 profile image
Lars Rye Jeppesen

You can try initial stuff in the 16RC0

Collapse
 
aminerhanemi_79 profile image
amine rhanemi

Thank you for the great article.
I was wandering how does Push/Pull Algorithm works when a consumer depends on two producer. Similar to the first name / last name example.
Why does the counter is equal to 2 and not 3? what it the equivalent of debounceTime(0) in signals ?

Collapse
 
goetzrobin profile image
Robin Goetz • Edited

Thank you! Those are some great questions. The reason the counter is equal to 2 and not 3 lies in this part of the official documentation provided by the Angular team:

Effects do not execute synchronously with the set (see the section on glitch-free execution below), but are scheduled and resolved by the framework. The exact timing of effects is unspecified.

Crucially, during this first phase, no side effects are run, and no recomputation of intermediate or derived values is performed, only invalidation of cached values. This allows the change notification to reach all affected nodes in the graph without the possibility of observing intermediate or glitchy states.

Once this change propagation has completed (synchronously), the second phase can begin. In this second phase, signal values may be read by the application or framework, triggering recomputation of any needed derived values which were previously invalidated.

In short, Angular will know that both first and last name changed at the same time before it re-executes the effect.

combineLatest on the other hand emits once for every input observable that changes. Even if they are changed at the same time. To avoid that we use debounceTime(0), to just wait long enough to skip the first emission and also push both new values at the same time.

Therefore, there is no equivalent of debounceTime with signals. It is baked into the mechanism.

Collapse
 
aminerhanemi_79 profile image
amine rhanemi

Very clear, thank you for the explanation.

Collapse
 
davdev82 profile image
Dhaval Vaghani

This is really awesome article. One thing I am not sure is if the change happens at a parent component? Will the child component need refreshing ? I am tempted to think no. With this fine grain reactivity with signals we know exactly which components template needs refreshing and therefore there is no need to refresh the templates child components. If indeed the child components signal have also changed, then its effect will get scheduled and therefore trigger a localized Dom update of the components template only (excluding the child components)

What are your thought ?

Collapse
 
goetzrobin profile image
Robin Goetz

Thank you! I am glad you enjoyed it!

We are still waiting on signal based change detection to be implemented, but the way I understood the Angular team members on the live stream with Ryan Carniato is in line with what you are saying here.

Change detection will not be too down anymore, which means that parent and child will be updated independently. Actually, the way I understood it, signals even enable updates on the binding level, which means that even within a template only the parts that changed would be updated.

We are still waiting for the RFC and initial PRs that enable this signal based change detection, but I am excited for when it is released and the amazing performance benefits that will come with it.

I hope that answers your question!

Collapse
 
tsharp profile image
timonkrebs • Edited

Great article. Thanks for putting this all together. I think one important thing is missing. Its the benefits for understanding the change detection since the reactive graph can be visualized. And likewise can be reasoned about this way: twitter.com/thetarnav/status/16251...

Collapse
 
goetzrobin profile image
Robin Goetz

Thank you so much for your comment! I will try to add a paragraph about this! I saw that Pawel from the Angular team tweeted about this too: twitter.com/pkozlowski_os/status/1...

Seems like they’re working on a tool similar to the one you mentioned!

This would be an absolute game changer for all of us!

Collapse
 
mandrewdarts profile image
M. Andrew Darts

This is awesome! Thank you for bringing all this together and doing a quick deep dive 🤘

Collapse
 
goetzrobin profile image
Robin Goetz

Thank you!

Collapse
 
thavarajan profile image
Thavarajan

Wow what an explanation, in particular i didn't even hear the signals, but the way you explain is awesome, Great read

Collapse
 
goetzrobin profile image
Robin Goetz

I am glad you enjoyed the article! Exciting times for Angular so I’m glad that my article helps more people to understand how big a deal this is!

Collapse
 
roverbober profile image
roverbober

This means that our fullName$ observable actually emitted twice. One time for each change to firstName and lastName. While it happened so fast that we could not see it, this component actually rendered a intermediate state just to be instantly replaced by the final, correct state.

That's not accurate (as far as I know). The component will not be rendered immediately after the 'firstName' is updated. The 'firstName' and 'lastName' changes occur within the same event loop task. Therefore, Angular waits until all calculations in the task are complete before rendering the component.

Collapse
 
goetzrobin profile image
Robin Goetz

You’re right! Here’s a little more on it: itnext.io/microtask-queue-rxjs-ang...

I shouldn’t say rendering. The tap operator executes and increases the value, but rendering is “debounced” until the microtask queue is empty… So rendering might be the wrong example, but if you used the inputs to e.g trigger a network request you’d send that request twice. Thanks for catching that! I’ll try to update the article!

Collapse
 
rakesh24386 profile image
Rakesh Kumar

Thanks for this article. This is really awesome.

Collapse
 
goetzrobin profile image
Robin Goetz

I am happy that you find it helpful!

Collapse
 
yksolanki9 profile image
Yash Solanki

Amazing article! Super easy to understand. Thanks @goetzrobin for writing this!

Collapse
 
goetzrobin profile image
Robin Goetz

Thank you very much! I am glad you enjoyed the article! Let me know if there’s other topics in Angular you’d like me to explore!

Collapse
 
aboudard profile image
Alain Boudard

Well mission success I must say.
You did an amazing job by browsing all the aspects of this topic ! Thanks a lot 👌

Collapse
 
goetzrobin profile image
Robin Goetz

Thank you! I am glad you find the article helpful!

Collapse
 
spock123 profile image
Lars Rye Jeppesen

What a great post, and yes this is so good.

Collapse
 
goetzrobin profile image
Robin Goetz

Thank you very much! I am glad you enjoyed the post!

Collapse
 
dmnchzl profile image
Damien Chazoule

I just finished to read your post (yes, until the end 😅) Nice job 👏 It's very complete! It's cool to see the concept of Signals appear in the Google framework. I can't wait to test this new feature...

Collapse
 
goetzrobin profile image
Robin Goetz

It’s a long one! I am glad you made it all the way to the end!

Me neither! This is such an exciting feature! I am super excited when they add the integration with template rendering / change detection. Then we will have a first look at this new (incredibly performant) way of building Angular applications!

Collapse
 
riethmue profile image
Sarah

Thank you so much for this article on Angular signals. I appreciate your efforts in putting this together, and it has definitely helped me to better understand signals in Angular.

Collapse
 
goetzrobin profile image
Robin Goetz

Thank you! I am glad it helped you!

Collapse
 
mattiamalandrone profile image
Mattia Malandrone

Great post! thank you!!!

Collapse
 
goetzrobin profile image
Robin Goetz

Thank you!