This is a simple and elegant explanation on solving double source of truth problem. This will be extremely helpful to document the pattern in Angular official documentation show case what a better alternative to effect.❤
We're all wrapping our heads around this, posting more this week from our follow up discussion with Ben Lesh. Feel free to come join our live calls, we're all learning this stuff together. 🥰
@@techstacknation Is the video for that talk with Ben Lesh already out? Since you said this week, and Im watching this 8 days later, but cant see a new video on this channel.
Hi Robin, here you go! 🤗 ruclips.net/video/sQ96BREhAJg/видео.html You can see videos like this sooner if you join our free community at TechStackNation.com and if you join our premium spaces, you can participate in our live calls! 🌟
It's mostly poor naming... Call it "watcher" and the React "instinct" will be suppressed, and more Vue folks will chime in and promote the right thing todo.
You’re right that this is important to consider. But consider this: it could be a paradigm shift that brings positive change. In the early days of component based frameworks, people were breaking things up too granularity and others had the opposite problem with giant 5k line components. I remember people saying then, “if people aren’t doing it right then maybe it’s a problem with the design,” but here we are years later and clearly that was the paradigm shift we needed in the web space. Signal-based state is another one paradigm shift and I think it’s absolutely a necessary one. The dependency graph and change detection optimization are more than worth it. It will take time for best practices to develop and for everyone to wrap their heads around it (just like when we shifted from templating engines to component-based frameworks). That doesn’t mean we shouldn’t pursue it.
@@xucongzhan9151 effect is the technical term. React didn't invent it. Learn your terminology. Vue calls it watcher to be edgy and it causes great confusion. Few developers seem to know that it's intended for effects.
@@ryanngalea to my knowledge they’ve never said it was a great way to listen to changes and I know some of them personally. From the beginning, even in the RFC discussions and the live coding sessions with an angular team member, they’ve always said you should use it seldomly. They even have that in the official docs where they talk about use cases for it.
While this approach works, it seems more like a workaround. Because overall what we are doing is finding a way to set a computed which is not the intended usage. Best scenerio would be that computed are made settable, then we avoid a workaround and things like `signal()()` All these point to a tool that requires we workaround its rules of usage for it to cater to our basic needs
Yep, I agree. The terminology alone is confusing. We're talking about something that is meant to be "computed"(), e.g. it's calculated, based on other inputs, but here it's not always computed, because we're sometimes setting parts of it manually. I don't think that'll ever feel right to me :D
I had the same problem but from the first time Alex started writing 'compu' I immediatly though of signal object used as a state approach. Signals are, to me, amazing and natural while RxJs is still a nightmare for my brain after so long haha! Thanks TSN for the great content and discussions!
Not sure how I feel about this. I feel like you could also just have used a setter method for your options-input and set the index signal to -1 THERE. Creating a new signal on every input change does feel a bit weird/hacky to me personally.
I don’t use effects.. but his example problem where he said “ok, but now options is an input that someone is binding into us..” then shows us how people’s solution is to add an effect in the constructor.. IMO that’s over engineering. The simple solution is just a getter and setter.
@@ryanngalea Its just that we think probably about switchmap when we see observable in observable. Its actually not really 1 to 1 with what they explained in the video. What he demonstrated in the video you would not even be able probably to do in RxJS cause you need then to subscribe to a newly created observable. The way usually you tackle it is to pipe some observables and put the new state into a subject where you call next. It works in the end similary, you pipe A and produce something and then you throw away everything in B. Its not a side effect in RxJS but it has some other issues. NOW I wonder if those similar issues you get by using this pattern - we will see. I think its hard really to discuss it now here in a chat. To me really I stay away from signals until probably 24 months pass. They are new, patterns will be discovered and then realised they arent so good. Its a new wheel. In my oppinion they can state "hey its needed for a reason" but you cant replace something like RxJS which was there 20 years by a new "primitive value".
Great stuff and very solid explanation. Definitely a pattern I can see becoming used in the future. Really lovely to see you guys ironing out the kinks with signals in the new way of working with Angular. Would love to see more of such content!
If you like the videos, you would love the live calls! Come join us on TechStackNation.com, the live calls are in our premium groups but it's free to join our community and you'll find extra videos there. You seem lovely, hope we see you there! 🤗
I'm not entirely sure, but this approach seems like it could lead to a memory leak in the HTML. With each tick of the computed function, a new signal instance is returned. This means the HTML has to subscribe to each new instance. But what happens to the previous signal? From the framework's perspective, I don't see any mechanism to stop observing the old signals until the component is destroyed. As a result, the number of subscribers might increase with each tick of the computed function, leading to a potential memory leak. Please correct me, if I wrong.
The thing is that those subscriptions are not the same as rxjs subscriptions, in the sense that you have to make sure to unsubsribe from them. When the computed runs, the signal previously created inside it is automatically 'disposed', removing it as 'dependecy' of any reactivity depending on it.
If I have a mat-table and a mat-sort I need to use viewChild to get the mat-sort, then assign it to a datasource - effect seems to be the right thing here. Because a data source doesn't seem right to recreate if things change when it has properties to update.
Excellent question zzing! If you'd like to create a quick stackblitz and post a link here, I can show it to Alex next time he stops by or, even better, you can join us on TechStackNation.com and ask him yourself! #YouCanSitWithUs 🥰
I was shocked by `effect` not running syncronously in the same frame as the signal's set operation. I thought all computeds and effects ran syncronously. Why on Earth is it not?
I am still not clear, how to manage end to end things like from component to service and call api response and track in component to do further operations based on response. Anyhow i need to use effect. Without effect i am not able to get the response. Did you get me? Or should I share the code?
For async stuff you should just use rxjs. That's the right tool for this kind of things. If you want a signal for your request/response, wrap them with some status field (idle, loading, success, error). Use toSignal with "idle" as initial value and emit the other states, when they materialize.
@@larshanisch but again that will also be a signal, and if any signal gets changed, to get the updated value in component we must use effect right? Or is there any other way around?
@@prasoon2510 later this day I will show my little helper function, which I use to turn an async service call into a signal including error handling. Than it will be clear.
Have seen people using the input transform function as a setter. Would be interested in your opinion on that approach. I have used it myself and it works well but I can't shake the feeling it is a bit of a hack. Also rather than using allowSignalWrites I tend to use the untracked function. Agree that it's best not to use effects and I do my best to avoid it if I can, like I avoid manually subscribing in RXJS.
I had these desync states with so many tables and data models at work. I really really hated it. Using layers of components and having 3-4 different table components just so someone else thinks they fixed it was insane. It showed that nobody at my office knew how exactly rxjs works. When I did a prototype with something simpler like svelte everyone just yelled at me saying omg its just another framework. Bro you don't even use the one you have properly. either learn the tools and have standard way with your team or use something simpler.
what about required inputs? when i try to create a signal that is based from an input that is required, i get an error 'Input is required but no value is available yet'. any ideas how to correct this implementation?
I've recently used an effect to setup/remove event listeners on a set of view childrens based on a boolean signal input (no signal writes), I suppose that's a good use case for effect.
There is one issue: if myName is set to 1 by a parent and child overrides it later, then if parents want to override it again with 1 since myName is now having a different overriden value, it will not work, signal are not allowed to be set with same value even if you set equal: (a,b) => false I will not be an issue when using objecr refereances
So there's one case which I haven't found an effect free solution for and it is regarding triggering outputs, depending on certain signal values... Is there anything I could do to get rid of the effect, e.g.: number = signal(0); isEven = output(true); effect(() => { this.isEven.emit(this.number() % 2 === 0); });
Though using `effect` under the hood you could use `toObservable` together with `outputFromObservable` here with: isEven = outputFromObservable(toObservable(this.number));
So instead of make it explicitly an effect, you put it in an object which has a signal in it and return that object as a computed value, and by that design the index-signal gets reset every time the options change. So it is still an effect of changing the value of the options, but then hidden within a (not so obvious imo) code construction. And for this to work you also have to wrap 'normal class properties' in another object (in this case called 'state'). In my opinion this looks more as a workaround than intentional. And now the signal for the index gets thrown away and the signal for the options is not, why is that a logical thing to do? why is it logical to recreate signals?
when you select index doesn't it recalculates state because state is not dependent only on options but also on index? so when you select index it recalculates and sets back index to -1 Also why: name = input('') myName = computed(()=> signal(this.name())) instead of public myName = computed(()=> this.name())
@@wartab jeezzz... do you understand what's happening there? I'm not talking about what he wants - i understand what he wants. I'm talking about bug in his computed implementation. When options changes he want to reset index to -1. He don't want to reset index to -1 when index changes - but that's what will happen. Since computed depend on index and options then when he do select(idx: number) then it changes index to selected index and then computed is recalculated(because index changed and computed depends on index) and index is set back to -1 and that's not what he wants - that's bug in his implementation with computed.
I like this and I'm okay with this (creating a signal inside a computed is so cool!) but please don't remove the ability to set signals inside of effects lol. There's not a good way to enforce it because you can very very easily write the sets inside of a queueMicrotask (at least I'm 99% sure) and reactive systems cannot track that right now, cause it'll push that logic to happen after the current stack frame (wrapped in the current signal context) is done. Having experimented a bunch with Vue over the weekend, their reactivity APIs are significantly nicer because they give you the ability to shoot yourself in the foot with setting signals inside of effects. There are a lot of things that you can do with effects that you can't just do with computeds, especially when needing to write wrappers to turn other data types from other libraries into a signal.
don't think they will remove the option to set signal inside effect. probably they'll just remove the necessity of putting an {allowsignalWrite: true} option. they will make it as default if im not wrong
So if you have something like this, using the old Inputs. _options: WritableSignal; _index: WritableSignal; @Input() set options(options: string[]) { this._options = signal(options); this._index = signal(-1) } It would work in the same way without the signal inside the signal approach
You should never "re-reference" a local signal (or in the rxjs-world an observable). Subscribers to the initial variable will not re-subscribe automatically to the new signal/observable and will lose reference. To reproduce try to include one of your _signals inside an `effect` and log their value. It will log the _options() and _index() once initially and won't log anything later due to the newly created references.
I don't think the way this video has introduced is a good practice, actually, you can derive the default selected state without resetting it: selected = signal('-1'); selection = computed(() => options().find(id => id === selected()) ?? null); And that's it, so clean so beautiful.
@@qyihamba7034 My approach here cannot reset selected to -1, but again not recommend the way the video was introduced. "State that updates together should live together". Alex's trick only let the state *live* together, but the way of updating the state is somehow indirection by computed API. The best & simplest solution for this is actually reducer mode. It not only lets the state be defined together, but also makes it update together.
This is a simple and elegant explanation on solving double source of truth problem. This will be extremely helpful to document the pattern in Angular official documentation show case what a better alternative to effect.❤
12:54 I understand the approach, but my brain immediately panicked thinking about `{{ myName()() }}`
Agree, it is not convenient.
We're all wrapping our heads around this, posting more this week from our follow up discussion with Ben Lesh. Feel free to come join our live calls, we're all learning this stuff together. 🥰
@@techstacknation Is the video for that talk with Ben Lesh already out? Since you said this week, and Im watching this 8 days later, but cant see a new video on this channel.
Hi Robin, here you go! 🤗 ruclips.net/video/sQ96BREhAJg/видео.html
You can see videos like this sooner if you join our free community at TechStackNation.com and if you join our premium spaces, you can participate in our live calls! 🌟
myName = computed(()=> ({is: signal(this.name()});
`Hi! {{myName().is()}}`
“If most users are using a tool incorrectly, it might indicate a flaw in its design.” Think about it. Maybe Angular team need mind-shift.
It's mostly poor naming... Call it "watcher" and the React "instinct" will be suppressed, and more Vue folks will chime in and promote the right thing todo.
You’re right that this is important to consider. But consider this: it could be a paradigm shift that brings positive change. In the early days of component based frameworks, people were breaking things up too granularity and others had the opposite problem with giant 5k line components. I remember people saying then, “if people aren’t doing it right then maybe it’s a problem with the design,” but here we are years later and clearly that was the paradigm shift we needed in the web space. Signal-based state is another one paradigm shift and I think it’s absolutely a necessary one. The dependency graph and change detection optimization are more than worth it. It will take time for best practices to develop and for everyone to wrap their heads around it (just like when we shifted from templating engines to component-based frameworks). That doesn’t mean we shouldn’t pursue it.
@@xucongzhan9151 effect is the technical term. React didn't invent it. Learn your terminology.
Vue calls it watcher to be edgy and it causes great confusion. Few developers seem to know that it's intended for effects.
It doesn't help that the Angular team marketed it originally as a great way to listen to changes, but now the narrative has changed.
@@ryanngalea to my knowledge they’ve never said it was a great way to listen to changes and I know some of them personally. From the beginning, even in the RFC discussions and the live coding sessions with an angular team member, they’ve always said you should use it seldomly. They even have that in the official docs where they talk about use cases for it.
While this approach works, it seems more like a workaround. Because overall what we are doing is finding a way to set a computed which is not the intended usage.
Best scenerio would be that computed are made settable, then we avoid a workaround and things like `signal()()`
All these point to a tool that requires we workaround its rules of usage for it to cater to our basic needs
Yep, I agree. The terminology alone is confusing. We're talking about something that is meant to be "computed"(), e.g. it's calculated, based on other inputs, but here it's not always computed, because we're sometimes setting parts of it manually. I don't think that'll ever feel right to me :D
@@delie32 This means linkedSignals is the angular teams final solution?
I had the same problem but from the first time Alex started writing 'compu' I immediatly though of signal object used as a state approach. Signals are, to me, amazing and natural while RxJs is still a nightmare for my brain after so long haha! Thanks TSN for the great content and discussions!
Thanks Mo for the lovely comment! Be sure to say hello if you join our community, would be great to see you in our live calls! ~Bonnie 🥰
This information was just what I needed!
I assume the upcoming linkedSignal in v19 will be a better alternative to this approach?
@@nathanaelmayne5424 yes exactly!
Creating new signals in computed looks really bad to me. I won't do this.
You misspelled badass
Not sure how I feel about this. I feel like you could also just have used a setter method for your options-input and set the index signal to -1 THERE. Creating a new signal on every input change does feel a bit weird/hacky to me personally.
I don’t use effects.. but his example problem where he said “ok, but now options is an input that someone is binding into us..” then shows us how people’s solution is to add an effect in the constructor.. IMO that’s over engineering. The simple solution is just a getter and setter.
Signals in signals is like switchMap in rxjs...
I thought the same. Literally.
@@muhamedkarajic because it's true. 😎
Can you outline the issue with rxjs switchMap?
@@ryanngalea Its just that we think probably about switchmap when we see observable in observable. Its actually not really 1 to 1 with what they explained in the video.
What he demonstrated in the video you would not even be able probably to do in RxJS cause you need then to subscribe to a newly created observable.
The way usually you tackle it is to pipe some observables and put the new state into a subject where you call next. It works in the end similary, you pipe A and produce something and then you throw away everything in B. Its not a side effect in RxJS but it has some other issues. NOW I wonder if those similar issues you get by using this pattern - we will see.
I think its hard really to discuss it now here in a chat.
To me really I stay away from signals until probably 24 months pass. They are new, patterns will be discovered and then realised they arent so good. Its a new wheel. In my oppinion they can state "hey its needed for a reason" but you cant replace something like RxJS which was there 20 years by a new "primitive value".
@@muhamedkarajic interesting thoughts. I have to play with this kind of idea in rxjs.
Great stuff and very solid explanation. Definitely a pattern I can see becoming used in the future. Really lovely to see you guys ironing out the kinks with signals in the new way of working with Angular. Would love to see more of such content!
If you like the videos, you would love the live calls! Come join us on TechStackNation.com, the live calls are in our premium groups but it's free to join our community and you'll find extra videos there. You seem lovely, hope we see you there! 🤗
I'm not entirely sure, but this approach seems like it could lead to a memory leak in the HTML. With each tick of the computed function, a new signal instance is returned. This means the HTML has to subscribe to each new instance. But what happens to the previous signal? From the framework's perspective, I don't see any mechanism to stop observing the old signals until the component is destroyed. As a result, the number of subscribers might increase with each tick of the computed function, leading to a potential memory leak.
Please correct me, if I wrong.
The thing is that those subscriptions are not the same as rxjs subscriptions, in the sense that you have to make sure to unsubsribe from them.
When the computed runs, the signal previously created inside it is automatically 'disposed', removing it as 'dependecy' of any reactivity depending on it.
Great explanation, thank you!
If I have a mat-table and a mat-sort I need to use viewChild to get the mat-sort, then assign it to a datasource - effect seems to be the right thing here. Because a data source doesn't seem right to recreate if things change when it has properties to update.
Excellent question zzing! If you'd like to create a quick stackblitz and post a link here, I can show it to Alex next time he stops by or, even better, you can join us on TechStackNation.com and ask him yourself! #YouCanSitWithUs 🥰
I was shocked by `effect` not running syncronously in the same frame as the signal's set operation. I thought all computeds and effects ran syncronously. Why on Earth is it not?
Very useful, thanks Alex and Bonnie 😊
Thank YOU for the appreciation, Andrey 🥰
I am still not clear, how to manage end to end things like from component to service and call api response and track in component to do further operations based on response. Anyhow i need to use effect.
Without effect i am not able to get the response.
Did you get me? Or should I share the code?
For async stuff you should just use rxjs. That's the right tool for this kind of things. If you want a signal for your request/response, wrap them with some status field (idle, loading, success, error). Use toSignal with "idle" as initial value and emit the other states, when they materialize.
@@larshanisch but again that will also be a signal, and if any signal gets changed, to get the updated value in component we must use effect right? Or is there any other way around?
@@prasoon2510 later this day I will show my little helper function, which I use to turn an async service call into a signal including error handling. Than it will be clear.
@@larshanisch awesome, that might help, else I will share peace of code which I want.
Hm, RUclips is not showing all my replys - seems, they don't like links...
Have seen people using the input transform function as a setter. Would be interested in your opinion on that approach. I have used it myself and it works well but I can't shake the feeling it is a bit of a hack. Also rather than using allowSignalWrites I tend to use the untracked function. Agree that it's best not to use effects and I do my best to avoid it if I can, like I avoid manually subscribing in RXJS.
I had these desync states with so many tables and data models at work. I really really hated it. Using layers of components and having 3-4 different table components just so someone else thinks they fixed it was insane. It showed that nobody at my office knew how exactly rxjs works. When I did a prototype with something simpler like svelte everyone just yelled at me saying omg its just another framework. Bro you don't even use the one you have properly. either learn the tools and have standard way with your team or use something simpler.
what about required inputs? when i try to create a signal that is based from an input that is required, i get an error 'Input is required but no value is available yet'. any ideas how to correct this implementation?
I've recently used an effect to setup/remove event listeners on a set of view childrens based on a boolean signal input (no signal writes), I suppose that's a good use case for effect.
Can we have reactive template form with this nested reactivity please
There is one issue:
if myName is set to 1 by a parent and child overrides it later, then if parents want to override it again with 1 since myName is now having a different overriden value, it will not work, signal are not allowed to be set with same value even if you set equal: (a,b) => false
I will not be an issue when using objecr refereances
So there's one case which I haven't found an effect free solution for and it is regarding triggering outputs, depending on certain signal values... Is there anything I could do to get rid of the effect, e.g.:
number = signal(0);
isEven = output(true);
effect(() => {
this.isEven.emit(this.number() % 2 === 0);
});
Though using `effect` under the hood you could use `toObservable` together with `outputFromObservable` here with:
isEven = outputFromObservable(toObservable(this.number));
So instead of make it explicitly an effect, you put it in an object which has a signal in it and return that object as a computed value, and by that design the index-signal gets reset every time the options change.
So it is still an effect of changing the value of the options, but then hidden within a (not so obvious imo) code construction.
And for this to work you also have to wrap 'normal class properties' in another object (in this case called 'state').
In my opinion this looks more as a workaround than intentional.
And now the signal for the index gets thrown away and the signal for the options is not, why is that a logical thing to do? why is it logical to recreate signals?
when you select index doesn't it recalculates state because state is not dependent only on options but also on index? so when you select index it recalculates and sets back index to -1
Also why:
name = input('')
myName = computed(()=> signal(this.name()))
instead of
public myName = computed(()=> this.name())
Because he wants to be able to override the name at the component level, which you can't do with a computed.
@@wartab jeezzz... do you understand what's happening there? I'm not talking about what he wants - i understand what he wants. I'm talking about bug in his computed implementation.
When options changes he want to reset index to -1.
He don't want to reset index to -1 when index changes - but that's what will happen.
Since computed depend on index and options then when he do select(idx: number) then it changes index to selected index and then computed is recalculated(because index changed and computed depends on index) and index is set back to -1 and that's not what he wants - that's bug in his implementation with computed.
I like this and I'm okay with this (creating a signal inside a computed is so cool!) but please don't remove the ability to set signals inside of effects lol. There's not a good way to enforce it because you can very very easily write the sets inside of a queueMicrotask (at least I'm 99% sure) and reactive systems cannot track that right now, cause it'll push that logic to happen after the current stack frame (wrapped in the current signal context) is done.
Having experimented a bunch with Vue over the weekend, their reactivity APIs are significantly nicer because they give you the ability to shoot yourself in the foot with setting signals inside of effects. There are a lot of things that you can do with effects that you can't just do with computeds, especially when needing to write wrappers to turn other data types from other libraries into a signal.
don't think they will remove the option to set signal inside effect. probably they'll just remove the necessity of putting an {allowsignalWrite: true} option. they will make it as default if im not wrong
So if you have something like this, using the old Inputs.
_options: WritableSignal;
_index: WritableSignal;
@Input() set options(options: string[]) {
this._options = signal(options);
this._index = signal(-1)
}
It would work in the same way without the signal inside the signal approach
You should never "re-reference" a local signal (or in the rxjs-world an observable). Subscribers to the initial variable will not re-subscribe automatically to the new signal/observable and will lose reference. To reproduce try to include one of your _signals inside an `effect` and log their value. It will log the _options() and _index() once initially and won't log anything later due to the newly created references.
what a beatiful solution
Effects, State.. My mind is having hard time wrapping around this! When did Angular come up with these?
It is in preview since angular 16, but i did not use signals yet too and still stuck with rxjs here 🙂
This is really great tips thank you very much!!!
Definitely
I don't think the way this video has introduced is a good practice, actually, you can derive the default selected state without resetting it:
selected = signal('-1');
selection = computed(() => options().find(id => id === selected()) ?? null);
And that's it, so clean so beautiful.
How do you reset selected to -1 when options is updated?
@@qyihamba7034 My approach here cannot reset selected to -1, but again not recommend the way the video was introduced.
"State that updates together should live together". Alex's trick only let the state *live* together, but the way of updating the state is somehow indirection by computed API.
The best & simplest solution for this is actually reducer mode. It not only lets the state be defined together, but also makes it update together.