4

[Watch This Space] Angular Reactivity with Signals · Discussion #49090 · angular...

 1 year ago
source link: https://github.com/angular/angular/discussions/49090
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

[Watch This Space] Angular Reactivity with Signals #49090

alxhub announced in RFCs

tl;dr: we've begun some prototyping work around adding signals as a reactive primitive in Angular, in advance of a formal Request For Comments (RFC) which we plan to open soon. Prototyping early and in the open aligns with our team values and allows us to get the most out of an RFC.

If you've seen our talk from ng-conf 2022, Angular: A Design Review 10 Years Later, you know we've been thinking long and hard about some fundamental design decisions of the framework. Last year, we kicked off a long-term research project with an ambitious goal: to embrace fine-grained reactivity in the core of the framework.

In a fine-grained reactive web framework, components track which parts of the application's data model they depend on, and are only synchronized with the UI when that model changes. This is fundamentally different from how Angular works today, where it uses zone.js to trigger global top-down change detection for the whole application.

We believe adding built-in reactivity to Angular unlocks many new capabilities, including:

  • A clear and unified model for how data flows through an application.
  • Built-in framework support for declarative derived state (a common feature request).
  • Synchronizing only the parts of the UI that needs updated, at or even below the granularity of individual components.
  • Significantly improved interoperability with reactive libraries such as RxJS.
  • Better guardrails to avoid common pitfalls that lead to poor change detection performance and avoid common pain points such as ExpressionChangedAfterItHasBeenChecked errors.
  • A viable path towards writing fully zoneless applications, eliminating the overhead, pitfalls, and quirks of zone.js.
  • Simplification of many framework concepts, such as queries and lifecycle hooks.

Changing the reactivity model of an established framework like Angular is a significant project, with numerous challenges. Our plan for this project is broken out into multiple phases:

  1. Research and development, experimenting with different approaches to reactivity.
  2. Selection of a candidate design.
  3. Prototyping of the initial design to demonstrate feasibility.
  4. One or more community Requests for Comment (RFCs) to explore tradeoffs and inform the final design.
  5. A developer preview of the core reactivity implementation.
  6. Iteration and expansion of the core implementation towards the full design.
  7. Collaboration with the community to bring the entire Angular ecosystem forward into a reactive future.

Over the last few months, we've progressed through the first and second stages of this project, and have converged on a design based on the well-known reactive primitive of signals. During our experimentation we felt that this design demonstrated the strongest alignment with our overall goals.

Signals are not a new idea in the framework space - Preact, Solid, and Vue all employ some version of this concept to great success. We've taken a lot of inspiration from these and other reactive frameworks, and we are especially grateful to Ryan Carniato of SolidJS for his willingness to share his expertise and experience in many conversations over the last year.

That said, requirements across frameworks differ widely, and we've designed our version of signals to both meet Angular's specific needs and as well as take full advantage of Angular's unique strengths. Even though it's still in the prototype stage, there are a few aspects of our design which we're particularly proud of:

  • Lazy evaluation, which is both efficient and avoids the need for explicit batching operations
  • A computation model which doesn't require immutable data
  • Flexible effect scheduling, allowing for seamless integration with Angular
  • Clever use of WeakRef to avoid explicit lifecycle management of signals
  • Possibilities around using our compiler to optimize various reactive operations, such as scheduling effects to automatically minimize reflows when performing DOM operations
  • Bidirectional integration story with RxJS reactivity

We've begun prototyping this design and will be integrating it into Angular over the next few months. We're committed to our open source spirit and plan to conduct these prototyping efforts in the open. This prototyping is essential to proving that our design for signals in Angular is viable and allows us to progress to a community RFC. The RFC will cover the rationale for using signals and the detailed design of various parts (our implementation, integration with RxJS, and other topics related to this effort).

We strongly believe that adding built-in reactivity to Angular is in the long term best interest of the framework and its users, and look forward to sharing more information about this project in the upcoming RFC.

When will the RFC be?

Sometime later this year, depending on how smoothly the prototyping efforts progress.

Why signals as the reactive primitive?

This is a great question, and one which will be thoroughly discussed in the upcoming RFC. In short, signals have many of the properties we identified as desirable in a reactivity system designed for Angular, including:

  • Values that are always available (synchronously)
  • Reading a value does not trigger side effects
  • Consistency between values (reads can't show inconsistent state)
  • Implicit, low overhead subscriptions
  • Automatic and dynamic tracking of dependencies

Why are you committing code before the RFC?

There are a few reasons why we've chosen to begin prototyping before opening an RFC:

  1. We feel having a real prototype will amplify the value we can gain from an RFC, as we will be able to share more detailed designs and ask more precise questions.

  2. In any major design, there are challenges and constraints which only become visible at the scale of a full prototype. We want to uncover these and account for them in the RFC itself.

  3. Many technical problems are unrelated to the overall reactivity system design (such as how it will integrate into Angular's change detection algorithm). We want to tackle these problems sooner rather than later.

  4. Google's internal codebase has a different set of build tools and constraints than the external ecosystem, and early prototyping is necessary to validate our designs in that environment as well.

You must be logged in to vote

Replies: 33 comments · 139 replies

I advise you to take a look at the following signal implementations: artalar/act, nanostores/nanostores.

You must be logged in to vote
0 replies

Amazing. Can't wait to see the prototype smiley

You must be logged in to vote
2 replies

Here is the prototype of the signals lib: #49091

Here is the prototype of the signals lib: #49091

So basically, it is the Reactivity API from Vue with different function names?
Do you have any idea yet how we use it in our HTML templates? Will it be always the function call to extract the value?

In Vue, it is possible to just use the property name, which makes it easier.

Thank you for the transparency. I've taken a look at the code and "WOW!". This specific test puts a smile on my face.

image
You must be logged in to vote
0 replies

Here are a few libraries you may find interesting for the prototyping phase: https://github.com/damianstasik/awesome-reactivity

You must be logged in to vote
1 reply

I hope we'll one day make the list!

I would also add Effector to the list https://github.com/effector/effector

You must be logged in to vote
0 replies

I do not like dirty functions (functions with the internal state, AKA “hooks”), I think that functions should be pure and for keeping the state we have class instances.

So, the fact the “hooks plague” is corrupting Angular is bad news for me, but if it will help us to get rid of zone.js - amazing. Because zone.js is much worse.

You must be logged in to vote
9 replies

I would argue "the best of two worlds". There is a simplicity mirage when people see signals from far away, but when they get near, they understand how ugly and bad it is. For example, having two types of "for" is a bad design, no matter how you look at it.
https://www.solidjs.com/tutorial/flow_index

Having two types of "for" is a side effect of fine-grained reactivity. Angular will eventually need such bad designs as it is baked into fine-grained reactivity.

For these kinds of designs, even it's impossible to write lint to catch wrong usages. It is a nightmare for big projects.

Or sometimes you need to manage your dom rendering changes yourself instead of the framework to take care of that.

https://www.solidjs.com/docs/latest/api#createrendereffect

And solid can cause max call stack limit if a project is big enough. Just imagine something like a role or permission that is used in every part of the application change.

https://stackoverflow.com/questions/7826992/browser-javascript-stack-size-limit

For every effect you keep adding, your memory usage will increase multiplied by the number of signals used inside that effect. Bcs every signal keeps track of its effect and its usage reference. That's a big no for low-powered devices.

Clean-up of the lost references effects and signal usages also is another story. If someone asks you if having a framework that keeps adding and removing to arrays on every function usage in your application, and it does it multiples by the number of signals in your application, is it a good framework? What would be your answer? And I want to remind you that every line of your template that uses a signal is an effect too!

Rx-angular solved the issue of the complexity of rxjs. I don't understand why we can't build on top of that!

For anyone who thinks the signal is a good design choice, I suggest trying to recreate some part of their application with it.

But rather than being forced to use rxjs you have a simpler option and 3rd party libs that do it much. Better.

Getting rid of Zone.js is a good direction but Signals looks like (another) revolution in the Angular world.
Also, it's really dubious that implementation with this syntax is well-suited to Angular.
IMHO decorators, as we see in MobX, are better fitted: https://mobx.js.org/enabling-decorators.html#enabling-decorators-
We also have a ready-to-use (zone.js-free) solution from @BioPhoton (Michael Hladky) : https://www.rx-angular.io/
Please consider going this way first because Angular is heavily related to RxJS and this is ok. Otherwise, we will create the same mess as we saw in React - Redux and Hooks.

Yeah, it would be interesting to know what other options were studied. And Rx Angular or NgRx were not good enough in comparison to signals.

+1 for MobX decorators and rx-angular approach, rather than yet another "revolution" in Angular.

So basically after 7 years of development you figured out that RxJS is "doesn't fit all the needs" huh?
Let me guess, after you release these Singals, you'll rewrite the Engine "based on our brand new Signals system" tm
How about focusing on the real things, like make HMR work in 2023? #39367

You must be logged in to vote
10 replies

I do need real HMR, and anybody who does more or less complex enterprise-grade application does. It was reloading whole your app without refreshing the browser page, nothing more. It's not HMR at all, in any sense. Just read through that issue, no need to continue an offtopic discussion here.

I was actually thinking along the lines of what @dgp1130 said I think after esbuild hmr will come into focus more, so it's not like the team is doing nothing, they are at least making progress toward it. You're a developer too, you understand, these things take time and you can't just choose what you want to work on whenever, I'm sure they want to give you proper hmr support, why wouldn't they want it? So there must be a reason and I don't think being toxic about it helps, a lot of things that we are getting now in Angular have been asked for years BUT WE ARE STARTING TO GET THEM, your hmr time will come, just support what's being done right now.

That's what happen for years now. They have "other priorities" like Ivy and now... what they call "Signals".
It's just an RxJS BehavriorSubject + shareReply + distinctUntilChanged out of the box. So why then all the fuss about? Just provide a new RxJS creational operator, that's it. But guess what, it won't change much. Because it's not the reason of failure.

You just illustrated why signals are great, not only do you have to use those extra operators to achieve the same thing, you have to learn them as a new developer and also be aware of any gotcha that can happen, also then you expect Angular to create a custom method just for that, why not use something the js ecosystem is adopting , if you want to use RXJS still and know how to no problem but you can see it already makes Angular simpler because if you get a new developer instead of telling them use all those operators, just create a signal, and now if they want to use solid or something else the knowledge is re-usable in much less time.

Yes I want to use RxJS and I want Angular to improve their integration with RxJS instead of making up new things, that could turn out to be a failure in another 7 years. Another operator needed? No problem, make it. But not a hook-like "something else".

Angular is the best framework

You must be logged in to vote
24 replies

RxJS is synchronous as long as there's nothing async present in the pipeline like a delay.

import { BehaviorSubject } from 'rxjs';

const s = new BehaviorSubject(1);
let a = undefined;
s.subscribe((v) => (a = v));
console.log('a1', a); // 1
s.next(2);
console.log('a2', a); // 2

But it's easy to get something async into a pipeline that will the break all previous assumptions about if it's sync or async

And by "constructive" you mean to agree to you without arguing? Ok

Not at all. There's a difference between an exchange of ideas, and "arguing". You're just here arguing, with a "I hate this, you suck" energy. That's not constructive.

The main question is simple: why to add another concept different from RxJS, if it can me just +1 creational operator based on RxJS (it's extensible, you know?) that would combine BehaviorSubject, shareReply, distinctUntilChanged, and whatever else.

Ok, let's think how you could replace a single signal with rxjs implementation. For each signal you'll need to have a separate BehaviorSubject plus piped observable (shareReplay + distinctUntilChanged) for the optimization purposes to mimic signal's behavior. In this case you could use this observable in streams and use subject to read current value.

Now you need some computed from those "signal" observables. Ok, mapping them and also piping to shareReplay + distinctUntilChanged. But, hey, how to read current value from it if you need it in some non-rxjs code? Ok, adding tap and saving the value to some property. Great! Now instead of 2 signals and 1 computed (3 lines of straightforward code) you have 2 subjects, 3 piped observables, one simple property and tens of lines of boilerplate code.

And also don't forget to add dozens of | asyncs in the component template.

Just like it's done for EventEmitters. They are part of Angular and aware of Component lifecycle, there's no need to subscribe or unsubscribe, and still it's based on RxJS. Right? I'm not saying "don't hide the implementation logic", I'm saying that maybe - just maybe - Angular doesn't need another one reactive solution apart from RxJS that's already there.
It could be something similar to how they do it in NGXS:

@Signal(...) value$;
// Or maybe 
value$ = new Signal<...>(...); // Signal extends BehaviorSubject just like EventEmitter extends Subject

// It's already aware about the component lifecycle, shareReplay + distinctUntilChanged is baked in.
// And you can extend it with _more_ logic if you want,  but it's still RxJS BehaviorSubject.

The main question is simple: why to add another concept different from RxJS, if it can me just +1 creational operator based on RxJS (it's extensible, you know?) that would combine BehaviorSubject, shareReply, distinctUntilChanged, and whatever else.

Ok, let's think how you could replace a single signal with rxjs implementation. For each signal you'll need to have a separate BehaviorSubject plus piped observable (shareReplay + distinctUntilChanged) for the optimization purposes to mimic signal's behavior. In this case you could use this observable in streams and use subject to read current value.

Now you need some computed from those "signal" observables. Ok, mapping them and also piping to shareReplay + distinctUntilChanged. But, hey, how to read current value from it if you need it in some non-rxjs code? Ok, adding tap and saving the value to some property. Great! Now instead of 2 signals and 1 computed (3 lines of straightforward code) you have 2 subjects, 3 piped observables, one simple property and tens of lines of boilerplate code.

And also don't forget to add dozens of | asyncs in the component template.

Well, it may be a bad news for some, but when you start using RxJS somewhere in your app, it is much easier to use it everywhere to avoid what you've discribed. Add a declarative pattern to it and (after you get used to it), in my personal opinion, it will become incredibly confortable to use.

Exciting development. Thanks for all the work and for the team's transparency along the way.

You must be logged in to vote
0 replies

I hope it will not have too much impact on the existing development model.

You must be logged in to vote
1 reply

Here's how it's gonna be, remember my words:

  1. They'll say "we invented new cool thing, Signals! It IS NOT a replacement to RxJS, rather a great addition!"
  2. (After 6 months) "Angular Signals are better than RxJS and here is why"
  3. (After anohter 6 months) "RxJS is deprecated in favor of Angular Signals, but here are our semi-working schematics to ease a migration process for you"
  4. (After anohter 6 months) "We have re-written our Engine, now it's called Angular PopCorntm, all libs should migrate or else they won't work"

Hopefully HMR is done by that time.

Awesome!
Maybe the mental model behind the signals can implement the InteropObservable type to build the bridge between rxjs and signals, translating between both will ease!

You must be logged in to vote
4 replies

@menosprezzi The concepts signalFromObservable() and observableFromSignal() are in the planning.
So there is going to be full interoperability between the two concepts.
They complement each other and do not take anything away!

What signals really open up in the feature is an alternative to zoneJs. I'm really looking forward t that future!

@SanderElias nice! But I mean that implementing InteropObservable interface you can use the signal directly in your rxjs pipes if you need.

For instance, lets say that angular changes the @input to wrap values into a signal, you can still use it with the rxjs's reactive utils if you need:

class MyCmp {
  @Input()
  value: Signal<number>;

  ngOnInit() {
    const aNativeObservable$ = this.myService.get();
    combineLatest([this.value, aNativeObservable$]).pipe(/* and so on :D */ ) 
  }
}

This will ease the migration between both!

@menosprezzi The concepts signalFromObservable() and observableFromSignal() are in the planning. So there is going to be full interoperability between the two concepts. They complement each other and do not take anything away!

What signals really open up in the feature is an alternative to zoneJs. I'm really looking forward t that future!

I think signalFromObservable() is really essential to have in place when this lands. It also has to automatically handle the subscription. If this gets done right it will be a really nice solution.

wou, what @menosprezzi said is interesting

I kinda want to stress-test this RFC with some questions that the Angular team may want to consider:

  1. How will affect RxJS and the Angular ecosystem? Many libraries and frameworks rely on both Angular and Rx together, so replacing the reactivity system will be a major blow to RxJS and possibly the entire Angular community.
  2. How will this affect Angular itself? For instance, @angular/common/http heavily relies on RxJS, so this RFC may need to consider how much of the core API needs to be changed.

Those are just some questions I can think of at the moment, and I'll update this comment when I think of more questions. I am sorry being a pessimist with this RFC, and I am not trying to shoot this RFC down; however, RxJS is a critical part of Angular, and replacing it will cause millions of websites to break if this RFC is not handled carefully. If this RFC goes through, I would suggest creating an extended LTS version or a compatibility mode, so websites have a good window of time to migrate before the pre-RFC versions sunset.

You must be logged in to vote
3 replies

They mentioned it 2 times:

Significantly improved interoperability with reactive libraries such as RxJS.

Bidirectional integration story with RxJS reactivity

I think it means that they are taking care of it and you don't need to worry about your existing code, relying on RxJS. Also, this tweet might be helpful

And if you're an RxJS master, you'll be able to leverage our RxJS interop to compose Observable inputs, queries, lifecycles into your streams.

@e-oz Sorry... man_facepalming

No @IRod22 you shouldn't be sorry, because you are right: there will never be "magic", even though they claim there is, like with NgUpgrade. Any interop will help us only partially, but there definitely will be a need to worry about existing code.

Thank Angular team! I have been watching the progress of optional zone for many months. It is fantastic to adopt signal fine-grained reactivity to Angular core. Will the signal be able to run outside of component like Solidjs? If it does, that will be super helpful to reuse/organize many common logics.

You must be logged in to vote
1 reply

@BruceWeng Yes, they will be available wherever you want to use them. You can use them in services and functions and wherever else you might need a reactive primitive.

wow congratulations partying_facepartying_face

You must be logged in to vote
0 replies

Should be a great addition! I guess it might change how we look at state management. Is there any thought how signals can enhance state management? Or maybe even a native angular solution?

You must be logged in to vote
0 replies

Really excited to see how the Angular team implements and builds around Signals. I think this is a good step towards dealing with Zone.js just as Standalone Components do with Modules.

From an implementation standpoint, what benefit does a BehaviorSubject provide over a Signal? Are there specific cases where I’d want to choose a BS over a Signal?

You must be logged in to vote
5 replies

the main point is you don't need to handle subsription/unsubscription with signal compare to Rxjs Subject

Thanks! Came here to ask the same question! Not needing to subscribe/unsubscribe would be awesome.

@ntorrey yeah imagine not having to do | async and deal with default null value

the main point is you don't need to handle subsription/unsubscription with signal compare to Rxjs Subject

That’s sort of my point. What benefit would a BehaviorSubject provide if a Signal can be easily transformed into an Observable? Seems like it’d save people from the private BehaviorSubject + getter/setter pattern I’ve seen in quite a few codebases.

@Cjameek you'd still want to have Subjects to handle streams of events or asynchronous events. Eg: a search input with debounce.

@Component({
   template: `
      <input type="text" (change)="query$.next($event.target.value)" />
      <p>Debounced query: {{ query() }}</p>
   `
})
export class SearchComponent {
   readonly query$ = new BehaviorSubject('');
   readonly query = toSignal(this.query$.pipe(debounceTime(250)), '' /* maybe default value since signals are synchronous */);
}

I'd like to put another problem in discussion: How about the learning curve of Angular?
It has always been said that Angular has a steep learning curve compared to its peers.
Won't this increace the learning curve? Having to learn now RxJs and Signals? Won't this probably move away new comers?

You must be logged in to vote
8 replies

why signals if we have already reactivty via Rxjs?
other frameworks are missing Rxjs integration, that's why I guess they implemented Signals as a core feature in their library.
maybe I'm wrong

One important question. What about all rxjs stuff that is introduced in simplest examples like @Output and EventEmitter. What about router events, reactive forms, httpClient. Will they move away from rxjs? That is really important thing to know to properly aproximate new learning curve

Rxjs is awesome, I use it also in Vue a solidjs, mapping rxjs observables to their signals.
Signals are simpler for sure, but also much less powerful.

And also, ecmascript is considering including native observables, they are already in stage 1.

I think with observables is going to happen like with promises 10 years ago.

Promises only existed in jQuery and libraries and finally were natively implemented in js.

why signals if we have already reactivty via Rxjs? other frameworks are missing Rxjs integration, that's why I guess they implemented Signals as a core feature in their library. maybe I'm wrong

I think this is more of an internal implementation for updating the DOM. Right now every change detection cycle requires a scan of all the data model to see if something needs updating. This is the zone.js model; it encapsulates all the javascript tasks and tells the framework when the tasks are done, then the data model scan. It is expensive and has quite a few gnarly issues that we have to work around. The signal implementation would, if I understand it correctly, have each property of the data model subscribed to by the dom manipulation system to know when something changes.

From the user side, these primitives can be useful for simple data manipulation and update situations, which is most of the time. For more complex data structures and data flows full of asynchronous events, rxjs has more tools for dealing with them, along with the complexity and learning curve. Hard problems require powerful tools, simple problems are best with simpler tools.

I think actually the learning curve will be simplified probably the end goal will be that you don't have to learn rxjs to use Angular but you can use it if you want and are willing to learn.

Would this be needed at all if Angular would handle change detection like Svelte does? Can't this whole thing be done fully in the compiler so no user-code would then needed to migrate as long as it's fully OnPush changedetected?

As for signals, as long as I can pipe out of a signal like with observables and has the familiar Subject API like .next(), .pipe(), .subscribe() it's gonna be okay, like the EventEmitter. And as I see in the proposal Bidirectional integration story with RxJS reactivity this should be a given.

Implementation wise I see this this is a tradeoff of code shipped and performance. And since rxjs is already shipped, building a Signal out of rxjs building blocks is practically free. And then it would really be like EventEmitter, as subject that integrates with the component lifecycle and unsubscribes when no longer needed (and then some, like distinctUntilChanged etc)

And performance wise, usually a tailor-made solution is more performant but I think a thorough benchmark should be done comparing these two Signal implementations.

You must be logged in to vote
1 reply

Exactly. If there's an EventEmitter -- still based on RxJS -- why to add another "reactive primitive"? It also unsubscribes and automatizes things for you.

building a Signal out of rxjs building blocks is practically free

Also from conceptual and learning point of view.
Could be something like:

value$ = new Signal<...>(...); // Signal extends BehaviorSubject just like EventEmitter extends Subject.

As this thread seems to be as much about opinions on pros/cons for exploring signals versus rxjs/rxangular/mobx:

My 2 cents:

We have been developing an application over the last years that heavily relies on reactivity.
There's dozens of observables active at any time.
In my opinion, we would most probably have much more difficulty developing & maintaining the application without reactivity/RxJS.

However, working with RxJS does have its pitfalls, all of the ones have been summed up somewhere in this RFC.
If the angular team thinks/hopes they can deliver a solution that reduces/removes those pitfalls I'm all for it.
Be it an improvement on RxJS as-is, or be it signals, most important for us is that it makes working with reactivity in Angular easier/more intuitive.

If the Angular team thinks that signals potentially would do that, great.
If in the end signals turn out to be an flawed solution I'm sure that the team will hear that as feedback and RxJS will not be removed/replaced.
Possibly they will try a different approach taking into account the learnings from the signals experiment.

As such: worst case: effort being spent by the team towards signals and it doesn't work out. No problem for us, we just continue to use RxJS.
Best case: the signals experiment is a success and we can decide if it makes sense for us to migrate some or all of our application over time.

Long story short: I'm all for it.

You must be logged in to vote
3 replies

Or, the team could spend energy on improving e.g. debugging of RxJS via e.g. some kind of panel in Angualr DevTools. Or building up new operators and abstractions based on RxJS. In other words, improving the RxJS integration.

That's too optimistic. I can't remember a single "RFC" where team would change their decision. If they published something - it's already decided. The only useful thing we can do here is to remind them what cases they didn't test.

Improving on RxJS seems like a good alternative option.
Indeed, there are certainly different possible routes that can/should be considered, discussed and researched.
But I agree with @e-oz that #49091 means they internally have already done that (and following https://twitter.com/pkozlowski_os I've read that happening) and for now have taken the decision to explore the signals route because it holds (most) promise in their opinion.
Up to all of us over the next few months/year to try it out and see to what degree that holds true; and IF not, see how they adapt course.

I just tested Signal, it's easy to use but I don't see the difference compared to BehaviorSubject, except for other improvements (zone.js, Change Detection), so why was it added?

You must be logged in to vote
14 replies

Which is currently a much "weaker" version of rxjs based effects for async stuff. So I guess it should somehow play together

What I will also like to see is how would it play with many state managements concept of effect functions. Many effects are not based on value change but based on actual DOM event and only using current value of the state (or selector). Currently there are many problems like the diamond problem or batching problem which can be solved with denouncing (making it async) which as a result make effects using selectors more complicated. It seems signals can solve those problems entirely if they are lazy, batched and always updated.

It seems signals can solve those problems entirely if they are lazy, batched and always updated.

Yep, this is definitively a nice property of the signals approach. Especially with the proposed implementation where signals / computed are lazy by default and effects are scheduled to run in a separate JS stack frame.

It seems signals can solve those problems entirely if they are lazy, batched and always updated.

Yep, this is definitively a nice property of the signals approach. Especially with the proposed implementation where signals / computed are lazy by default and effects are scheduled to run in a separate JS stack frame.

The only thing I am missing is how the signal's effect (sync regular code) can be "merged" with async rxjs code requiring most updated signal value. Currently in places an effect function requires a new microtask to access denounced selectors values a chain of those effects will run each in its own microtask. Will signals be able to make it sync? Or the same concept but easier to handle? I kind of can't wrap my hand around it - as signals and computed are sync while signal's effects are kind of async by nature if I understand correctly.

Probably use of custom scheduler, but this should partially be handled by signal.toObservable() or something like this maybe?

The Angular team is working on adding a fine-grained reactivity system to the framework, which will allow components to track which parts of the application's data model they depend on and be synchronized with the UI only when that model changes. This is different from how Angular works currently, where it triggers global top-down change detection for the whole application. The team has been prototyping the design using signals as a reactive primitive, and plans to open a Request For Comments (RFC) soon. The goal is to unlock many new capabilities and simplify many framework concepts, with a viable path towards writing fully zoneless applications. The team plans to collaborate with the community to bring the entire Angular ecosystem into a reactive future.

You must be logged in to vote
4 replies

This looks like ChatGPT

Yes, always takes expert advice! :)

Should Angular move to Signals?

ChatGPT:
_I'm not entirely sure what you mean by "signals." If you're referring to a specific technology or programming paradigm, please let me know and I can provide a more specific answer.

Assuming you're referring to signals in a broader sense, such as a system of communicating between components or modules within an application, my answer would depend on the specifics of the situation.

Angular is a popular web application framework that provides a lot of built-in functionality for building complex, scalable applications. It uses a component-based architecture, which allows you to break down your application into smaller, reusable pieces. Components can communicate with each other using a variety of mechanisms, including input and output properties, services, and observables.

If you're currently using Angular and are considering switching to a different approach for component communication, it's important to carefully evaluate the pros and cons of any alternative approach. Signals may be a valid option in some cases, but they may not be the best choice for every situation.

Ultimately, the decision of whether to use signals or any other approach for component communication depends on your specific needs and goals for your application. If you're unsure which approach is best for your situation, it may be helpful to do some research and/or consult with other developers who have experience with similar projects._

ChatGPT results depends upon user's input. Dump input, Dump answer. Smart input, smart answer.

Please consider exposing this API "only" through Decorators!

After seeing the proposed reactive API and tryouts from people here and there I believe using this API with functions like signal(), computed(), etc. would make Angular a worse copy of Vue composition api or Solidjs. But you have the chance to provide a better one with decorators. Here is why.

Looking at this from purely DX perspective current Angular is already reactive enough. When we want to update an element we just set the property of the component class and thats it. I know you can keep it that way by using decorators. Vue or Solid cannot do that simply because they don't have class component to utilize decorators and reactive primitives needs a way to be subscribed for changes such as

// vue.js
const count = ref(0);

effect(() => {
  console.log(count.value)
})

count.value++;
// solid.js
const [count,setCount] = createSignal(0);

createEffect(() => {
  console.log(count())
})

setCount(count() + 1);

In Vue.js read/write (subscription/notify) is done by accessing or mutating the value property of reactive primitive and In Solid.js same principle is done by calling the signal value and the setter respectively.

For angular we don't need to define any extra getter/setter or value property for a reactive primitive since we work with classes and we have "this" to access any property for read/write. Here is an example of a possible reactive service via decorators. (This syntax applies for components too)

@Injectable()
class MyService() {
  @signal()
  public count = 0;

  @effect()
  private logCount() {
    console.log(this.count);
  }

  @computed()
  get isEven() {
    return this.count % 2 === 0;
  }
}

And when we want to consume this service we simply access or mutate it as normal.

//  in some component
const isEven = this.myService.isEven;

this.myService.count++;

instead of

//  in some component
const isEven = this.myService.isEven();

this.myService.count.update(p => p+1);

My final concern about exposing this reactivity API through functions is that it does not force you to use classes and can be written anywhere in your codebase. This may sound like a good idea to many developers but it's not. After a while we will start seeing projects ditching classes, services and DI completely because it will be tempting to write less code at first without anticipating the complexity as it grows. Exposing the API only by decorators will enfoce the current Angular's class based architecture which makes it scale better than any other framework out there.

So my question is: have you already considered how to ship this API? Or is it too early before the actual RFC?

You must be logged in to vote
9 replies

Man I just want good future for Angular and us, the developers

So do I, and so does everyone here. And each person may have a different perspective on what that means to them. And that's ok. Each person's perspective is valid, but you've been jumping down people's throats if they have an opinion you don't agree with. Just be civil, and we'll work together towards a brighter future.

What did I say wrong? Re-read my comment. What's invalid there? Don't take it personal, please, it's not about you. Allow people to have their own opinion. P.S. I'm sorry if I hurt you though hugs

Well, if you want functional everything, TSX, Redux, hooks, etc., why don't you just use React, Solid or Vue?

Not only does that misrepresent what I said (I said more functional, not functional everything) but it's also basically saying get lost and use another framework if you want it x way. That's hostile and not very inclusive of others' ideas. Whether you meant it that way or not.

But to your point on decorators being added to the ecmascript spec, they are, and that's great, but they're more limited than what TS' prior implementation was (e.g. using decorators in function params). And that's sort of what I meant by limiting how you can use signals in that way may be a bad trade-off.

First, I understand your point. But I also see what makes Solid so successful which is the focus on primitives. Angular also needs these primitives and once it's exposed to developers, we can build on top of it. Functions just compose better.
I think there is always an option to use them inside decorators. Check out this project https://github.com/lume/classy-solid

And the last thing, I would miss creating helper functions together with inject fn, if not exposed. Just my current view.

I hope that the entire JS execution environment can have reactivity, like this.

image

test demo
You must be logged in to vote
5 replies

What framework is this???

What framework is this???

@tayambamwanza This is a reactive JS execution environment built with dependency compilation-time semantic analysis and runtime Proxies.
https://github.com/FEFF01/WEBX

Hold my beer!

<div id="root">
  <div id="form">
    <input id="nickname" value="Jin" />
    <button id="clear">Clear</button>

    <label> <input id="greet" type="checkbox" checked /> Greet </label>
  </div>

  <p id="greeting">...</p>
</div>
// Take references to elements
const root = document.getElementById("root") as HTMLDivElement;
const form = document.getElementById("form") as HTMLDivElement;
const nickname = document.getElementById("nickname") as HTMLInputElement;
const greet = document.getElementById("greet") as HTMLInputElement;
const greeting = document.getElementById("greeting") as HTMLParagraphElement;
const clear = document.getElementById("clear") as HTMLButtonElement;

// Setup invariants

Object.assign(root, {
  childNodes: () => (greet.checked ? [form, greeting] : [form]),
  style: () => ({
    zoom: 1 / devicePixelRatio
  })
});

Object.assign(greeting, {
  textContent: () => `Hello ${nickname.value}!`
});

// Set up handlers
clear.onclick = () => (nickname.value = "");

@nin-jin In a reactive JS & HTML hybrid language execution environment, it can be written like this.

let nickname="EF";
let showGreeting=true;

document.body.appendChild(
    <div id="root">
        <div id="form">
            <input id="nickname" value=@(nickname) />
            <button 
                id="clear" 
                onclick=function(){nickname=""}
            >Clear</button>
            <label>
                <input id="greet" type="checkbox" checked=@(showGreeting) /> 
                Greet
            </label>
        </div>
        @:if(showGreeting)
            <p id="greeting">@(`Hello ${nickname}`)</p>
    </div>
)

I hope that the development process of frontend frameworks does not require more hacks, and the ultimate goal of frameworks is to enable developers to write UI interactions just like writing JS logic.

We have just gone through a very long process of upgrading to Angular 15 from Angular 14 which tooks us over 1.5 months to complete due to all the Angular material changes.

We have a VERY large project that we have been developing since Angular 2.0 Alpha with a massive undertaking and many developers involved. We just hope and pray that whatever path the Angular team chooses, that it is 100% backwards compatible as we want to continue and progress forward with Angular versions. Or, have a cli-upgrade migration, which honestly may be like voodoo magic as it sounds like this would be a challenge.

In any case, PLEASE praypraypraypray make future Angular compatible with our current projects which is rxjs heavy, fully ng opinionated with ngrx etc; tldr; we followed all of Angular's team best practices over the past decade, please don't make us drop it all for the latest bells and whistles as we are perfectly happy with how Angular works today, it ROCKS!

You must be logged in to vote
0 replies

feels good that those problems are finally getting addressed. I kind of have already got used to using Subjects, ReplaySubject and BehaviorSubjects. However I understand the point the Angular Team has on using a more simplified approach and having the option to opt out of RxJS. Here are my take aways and problems I had using RxJS so far and what might be relevant for Signals.

When e.g. we use inputs you would have now to have a seperate signal and then set it? At least thats the way I see it from the first glance. This is simular to the RxJS problem which made us spawn always 1 inptu and 1 Subject tolding that input. However order of execution did matter and I'm not sure how well the effect addresses those things cause so far if I have a variable a$ and b$ as inputs if they are in the HTML written like <component [a]="1" [b]="2" /> we had to do thigns onInit cause we needed both to be set and only after that we could do some computation and react to changes.

This is a problem which will even occur if we use OnInit but at least at that point in time you should have both observables. Now I'm wondering how those things would work with Signals.

I find it odd that you have counter.set(0) but when you wnat the value you do counter(), why then not make it so that you do counter.get() or counter.get or counter.value. Still

The 'you will have to unlearn' things is definitly true and I don't wanna be a breaker but I would like to rais the concern that his is still pretty vage and questonable how well it will work. Definitly must be experimented with!

What I would like to conclude this comment is that RxJS isn't working in all scenarios as well mainly due to the fact that things will execute one after the other syncronously. You could delay (macrotask) the execution ... what I would like to see is how signals are going to address such scenarios -> maybe this is the Push/Pull Algorithm.

You must be logged in to vote
4 replies

Hi, just want to share my thoughts about the difference between streams and dirty checking, all this is about the traverse algorithm. Maybe it will be useful for an explanation.

scheme of the push and the mark + pull approaches

Also, for some graphs there could be even better way.

notification + pull scheme
You must be logged in to vote
0 replies

I create a simple application with angular material, I used Signal to control the dynamic display of a progress bar when there are ongoing HTTP calls, and I also used it to display the results of an HTTP call. I mocked the backend and used the delay operator to simulate the delay of an HTTP response,

I find that using Signal simplifies works a lot since there's no need to subscribe and unsubscribe, or use "next" to notify subscribers, etc. This is in contrast to using BehaviorSubject.

You can find my example here : https://stackblitz.com/edit/angular-cfjezb

You must be logged in to vote
0 replies

After some thought I believe this feature can make a great state management. Combining it with ngrx component store idea we can create a store service or function (local and global) easily (a very simplified version of course):
Functional approach:

function createCounterStore() {
  const backendService = inject(BackendService);

  const count = signal(0);

  const incrementByTen = () => count.update(s => s + 10);

  const doubleCount = computed(() => count() * 2);

  const someAsyncStuff = createEffect<void>(
    pipe(
      siwtchMap(() => backendService.call().pipe(tap(() => incrementByTen())))
    )
  );

  return { incrementByTen, doubleCount, someAsyncStuff };
}

@Component({
  template: `<div (click)="counterStore.someAsyncStuff()">{{counterStore.doubleCount()}}</div>`
})
class Component {
  protected readonly counterStore = createCounterStore();
}

Class based approach:

class CounterStore {
  private readonly count = signal(0);

  constructor(private readonly backendService: BackendService) {}

  public incrementByTen() {
    this.count.update(s => s + 10);
  }

  public readonly doubleCount = computed(() => this.count() * 2);

  public readonly someAsyncStuff = createEffect<void>(
    pipe(
      siwtchMap(() => backendService.call().pipe(tap(() => incrementByTen())))
    )
  );
}

@Component({
  providers: [CounterStore],
  template: `<div (click)="counterStore.someAsyncStuff()">{{counterStore.doubleCount()}}</div>`
})
class Component {
  constructor(protected readonly counterStore: CounterStore) {}
}

I think there are two kind of effects:

  1. The one like ngrx - for async stuff
  2. signal's effects - maybe for events? Once X changes do something. Something like effect(() => doSomething(count())) which is similar to count$.subscribe(c => doSomething(c)). But I am not sure about that use case to be honest. Anyway it's a bit confusing what effects mean as both are called effects...
You must be logged in to vote
5 replies

Yes, I was looking at some examples and, I'm slowly convincing myself to the Signals too :)

Another thing worth mentioning is what's the difference between:

const state = signal({a: 2, b: 'test'});
const state = {
  a: signal(2),
  b: signal('test')
}

And why should we use one over the other and when

Good question. And also do changes to the props of state objects trigger the signal updates. I guess if it's going through the .set() function, then it forces immutability anyway and will always trigger the update algorithm.

@Harpush This video can summarize the two cases you mention https://www.youtube.com/watch?v=GEu8hc3RKpw

The second approach is more appropriate for a State Management since each "property" of the State object can be referred to as a "Slice". Updating a Slice shouldn't signal (no pun intended) the other Slices

I think this requires real thought. On the surface it seems like a no question that option 2 is better. But if try and think of the actual usage I am not so sure. A simple example:

const state = signal({one: 2, two: 'test'});
const one = computed(() => state().one);
const two = computed(() => state().two);
state.mutate(s => s.one = 8);

// ----

const state = {one: signal(2), two: signal('test')};
const one = state.one;
const two = state.two;
state.one.set(7);

In both case one and two will update (the result) in the same way and won't trigger unnecessary further recompute or rerender. The only difference is that option 1 will run one when two changes and vice versa... but this is cheap and actually resembles ngrx selectors.
Currently option 2 still wins but it is less obvious.

Now for a more extreme scenario. I have entities in store bound to virtual scroll in UI. You can add entities, remove and update properties inside. Lets take addition of 1000 entities each with 10 properties inside. The UI will render only 10 of them due to virtual scroll.
Signals are lazy and that's awesome but there is one caveat - in option 1 we will create one signal and in option 2 we will create 1000 * 10 = 10,000 sugnals. Now this can affect performance negatively pretty easilly.

Now I am not sure which way is better and if there is one correct answer to begin with.

I just remembered after signals are implemented and zone.js becomes optional, that means Native Async Await right?

You must be logged in to vote
1 reply

I'm pretty sure, yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Category
Labels
core: reactivity Work related to fine-grained reactivity in the core framework
56 participants
and others

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK