you can use combineLatest({a: obsa$, b: obsb$, c: obsc$}) instead of([obsa$, obsb$, obsc$]) and skip .pipe(map((a,b,c)=>({a,b,c})) to get the same result with cleaner code
What a fantastic overview of RxJs. You taught so many beginner-friendly concepts with great examples. This video is on my list of things to share with new Angular devs.
Very good overview, your videos have been really helpful lately. We use this pattern in my job, so if a new junior dev were to start, I could just share this to get them up to speed ^^ For trivial usages like this, wehre you have 3 streams, and you pipe-map them to themselves, you can also create the object inside the combineLatest operator. Instead of passing in an array of sources, you can do combineLatest( { source1name: this.source1$, source2name: this.source2$ } ) It does exactly the same thing, just a bit less verbose to type out. Furthermore, if in your pipe you do need to use the streams ot do some data manipulation, you can access those streams by key, instead of by an array index, without the map operator.
Thanks! I have it in my head that usage is deprecated? I haven't really looked into it though so I'll play around with this and see if I like it better :)
@@JoshuaMorony My comments keep getting deleted if I link the rxjs docs, TH-cam thinks im a spam bot :D I thought the same, my IDE wrongly keeps suggesting that it is deprecated, while it is not. The old signature is deprecated, where you pass in multiple sources as multiple arguments. The Array and hte Object signatures are recommended. Same with the ForkJoin operator. // deprecated combineLatest(odd$, even$); // suggested change combineLatest([odd$, even$]); // or combineLatest({odd: odd$, even: even$})
I prefer the patten because it is an object it is always true and because the properties are using the async pipe they are null before emitting. Much cleaner in my opinion.
You are losing control over your concurrency as nothing is coordinating those subs. You're putting a lot of TS in the template which is a poor separation of concerns. You're also exposing a lot more of your class internals which promotes coupling. You could literally emit that exact struct from a Subject with a default value. I think this is worse in every way :shrug:
Another great video Josh ! I find the straight declaration > instanciation very nice, because sometimes TS compiler gives us hard time declaring the proper type when using Observables with all the possible undefined outputs.
I , at this verry moment, will be refactoring a page that receives a separate set of streams from multiple api calls into one stream and then display the a skeleton-text using only one condition. That will be fun. thanks for sharing!
Does using Reactive Forms for large form views change this approach? I generally just subscribe to my data from persisted storage and then set the reactive form data imperatively. At that point Reactive Forms is binding the view and model data.
learned new things, Very informative. @Joshua Morony Can you make videos regarding forms and how to handle them in an efficient way, it would be really helpful, Thanks✌.
Hi Joshua! Thankyou for sharing this video. If i may ask, can you sometime covering how to unit test with this kind reactive pattern? Thankyou so much before 🙏
Yes not sure if I have any videos on this already, but a vid on observerSpy (which is what I mostly use to test all my observables) I think would be interesting
The view model pattern is more general than just this specific context, but in general means creating some kind of object that has all of the values for your view. In this context, yes I am talking about combining everything into one observable stream that will emit the view model object.
I don't really ever use resolvers so maybe there is some interesting patterns I'm missing, but in my mind you would only use a resolver when you need to pre-fetch some data before navigating to the page.
It won't - if you are using OnPush only the value that changes in the vm will trigger change detection where it is used as an input, and if you aren't using OnPush then change detection is triggered for your entire component tree on every change anyway. In both cases though, it's probably important to clarify that Angular won't re-render everything in the DOM anyway, just what has changed.
Nice thing but the issue I believe with this approach would be if we use shareReplay for multiple subscriptions then firstly, we will have to trigger subscription again if we want to update data of template after let's say deleting some record. Secondly, we will have to call checkError again. There are solutions to this like a refresh subject in order to forcefully get data again but in the end there will be too many observeables for a basic thing.
Do you have an example of what you mean? There shouldn't be any issues with needing to fetch data again as that's the point of using the observable streams - we aren't pulling data out of the streams to put into our vm, the entire vm is composed of streams that will update when necessary.
A forkJoin will wait for all of its input observables to complete and then emit an array containing all of their values - just once. combineLatest will emit every time there are new values, and the input observables may not necessarily complete.
Thanks for the work, ignoreElements are totally new for me. What do you think, what action user needs to take when there is error. Giving option of retry is one solution, is that possible vm$ stream? Or are there better solutios?
Yeah there's a bunch of different things you can implement with streams, you can even implement automatic retries when there is an error (e.g. try making the same request to an API 3 times before finally giving up and erroring)
Great Job! Love your content! Just one question. Would it be better to use BehaviourSubjects with an initial value instead of combining Subjects with startWith operators?
I might be missing your exact meaning but sure if you can set something up as a BehaviorSubject that makes sense, but otherwise if you are dealing with an async stream that doesn't have an initial value then startWith is a good way to deal with that
Hi @Josua ! I really like this approach and it's a really nice pattern to have but don't we need to worry about performance? For example if I have a huge vm object and only one of the subject emmiting one value at some point this will cause the entire vm to emit that huge vm object with only one updated value. In other words, emmiting a entire object only because one subject emmited a value.
This shouldn't effect the rendering of your component because, as far as I understand at least, even if the vm$ is emitting all of the values when any of them change, as long as you are using OnPush it will not trigger change detection for any components using those values as inputs (unless they have changed) as they maintain the same reference.
I think of a use case which could potentially lead us to performance issue if we had some heavy computations inside of our derived vm$, especially if the vm$ itself was large. Thus for each emission inside combineLatest we would have to recompute everything again even though most of the output would evaluate to the same value as the previous one.
Really cool Rxjs stuff here..... But I wonder, what is the advantage of this approach? It seams that it can get really complicated with a more complex case. If there is no performance improvement, I prefer to keep with more readable code to be easily maintainable. In the real world of software development, this is the key. "Maintainable". I always keep in mind what will happen if in 2 years from now, another developper has to go into this code to fix a bug or add a feature. He should be able to understand how the code is working really fast. And I don't think that kind of advance use of Rxjs is really helping.
At least in my opinion, this makes things simpler. It makes it very explicit what data is intended to be displayed in the template, and there is one mechanism for doing that which will auto update the template as those values change. If you want to display something in the template, you add it to the vm$ stream. I think this provides a nice pattern/rule for many different devs to follow over time, rather than potentially different devs doing different things to display values in the template.
It creates a nice separation of "template" vs "template logic". Conceptually, I start from the template. "What's do I need to know in order to render this view in all of the states we need to support, with the simplest interface?". Then I fetch all the things I'll need (usually from API services, user inputs etc) and compose those streams together, finally mapping the data to my simple VM. It prevents you from adding a property to an API interface somewhere and cascading that down into the template. It also means we can easily keep complex logic out of the template and in the TS instead. E.g. `*ngIf="user.firstName && user.lastName && user.loaded && user.whatever"` we can just do `*ngIf="user.name"` and move the logic into the observable map().
@@ponderwonder I like your explanation. However, If the vw$ were more complex with more inputs to combineLatest and even more of these derived fields requiring additional pipeable operators to evaluate themselves (such as you described with map to obtain user.name) then I think we would still end up with pretty much the same mess as we never used vm$. In my opinion, the pattern in the video is very nice though it won't make much difference if we have to deal with bigger number of values inside the vm$. What are your thoughts @Peter Hodges @JoshuaMorony PS. Thanks for sharing this.
@@valeron_1337 Always welcome the discussion. I think I would argue the opposite to your logic. For very simple components that have a couple of angular inputs and a simple template, using the vm pattern can feel like overkill. And yeah, you can still keep template logic in the TS by using OnChanges to map to an internal property. As the number of inputs (general inputs, not necessarily angular inputs) increases, and especially where these inputs are asynchronous and can happen over time, the reactive nature of rxjs makes things much simpler. (Think DOM events, socket events, timers, polling etc.) Without it, it's really easy to get into inconsistent states due to race conditions, things happening in orders you didn't expect etc. With reactive programming, providing you've piped your streams together correctlt, you'll always get the correct result eventually. If a stream further up the chain emits out of sequence, or twice, unexpectedly, maybe the vm is emitted twice (a mistake) but you'll still end up with the correct result. The only time I've really seen it become a mess is where people combine reactive programming with imperative patterns (e.g. using a tap and updating a property on the class, or opening a dialog via a function invocation, rather than declaratively) but this is the vm pattern being applied incorrectly rather than a problem with the vm pattern itself.
Josh, excellent video as always! I am curious to hear what you think of the following approach as an alternative to your solution: In the template, you could use the following condition: *ngIf="{greeting: greeting$ | async, user: user$ | async, count: count$ | async} as vm". I used this approach a few times on my day job
this is bad practice for multiple reasons: 1. make your template as lightweight and stupid as possible. Therefore its easier to change stuff in die future. 2. now you have 3 async pipes in the template vs 1 async pipe. This does not scale in terms of performance and also you're bloating your template what violates Nr1 again. Imagine you have 15 properties with your approach
Still valuable content. Thanks a lot. You can also use an object in combineLatest if I am not mistaken. Maybe on the latest versions only? No need to map your value anymore. const fullname$ = combineLatest({ firstname: firstname$, lastname: lastname$ });
8:10 not gona lie - coming from Vue this looks... horrible. I really want to start liking Angular, but such structures seem over engineered and hard to read.
This example is so basic that I dont see any advantage of this approach. I mean with old-style 3x subscribe I gonna have pretty same html and js wont be more complicated either... And there are some questions, how this code will look like in more advanced, more real cases: E.g. lets say count$ is an input and user$ is http get with count$ as param -- can u deal this with no subscriptions in js. E.g. u need to calculate some value based on greeting$ and count$ -- where this code goes? If you add this to combineLatest thing, then its gonna run also when userDelayed$ is emitted. etc.
Coding reactively is a bit of a different mental model, but the general idea for the scenarios you have suggested is to derive streams in some manner using RxJS operators for whatever you want to add to the vm$ stream. For your count->user example you would have a user$ stream that is derived from the count$ stream and switchMaps to http. For greeting + count you would create a new stream with combineLatest that has both of those streams as input, then use the map operator (or whatever other operator you need) to get the result you want. All of these streams you create go into the single vm$ stream, and yes any time any of the input streams change the vm$ stream will emit all of the values, but this doesn't matter. If you are using these vm$ stream values as inputs to components the reference is maintained if the value has not actually changed, so you aren't unnecessarily triggering change detection for unchanged values.
biggest mistake in rxjs is error handling catchError((err) => of (err)) is such common and such ugly solution that it's surprising that no one in rxjs team came up with the idea to donst complete Observale when errors appear. Observable should be complete only in explicity way using .complete() method. Observable should be stream of errors, and success. Alternatively inside constructor whe shuld have oportunity to configure that behavior: new Subject({errorCompleteSubject: true})
Join my mailing list for more exclusive content and access to the archive of my private tips of the week: mobirony.ck.page/4a331b9076
you can use combineLatest({a: obsa$, b: obsb$, c: obsc$}) instead of([obsa$, obsb$, obsc$]) and skip .pipe(map((a,b,c)=>({a,b,c})) to get the same result with cleaner code
Thanks! Yeah I thought this was deprecated but discovered in another comment thread the deprecation warning in VS Code was not accurate
@@JoshuaMorony use IntelliJ instead.
Pro tip. Thanks
@@uziboozy4540you mean neovim right?
What a fantastic overview of RxJs. You taught so many beginner-friendly concepts with great examples. This video is on my list of things to share with new Angular devs.
I've been doing this for a long time now with no complaints! ❤
Very good overview, your videos have been really helpful lately. We use this pattern in my job, so if a new junior dev were to start, I could just share this to get them up to speed ^^
For trivial usages like this, wehre you have 3 streams, and you pipe-map them to themselves, you can also create the object inside the combineLatest operator. Instead of passing in an array of sources, you can do
combineLatest( { source1name: this.source1$, source2name: this.source2$ } )
It does exactly the same thing, just a bit less verbose to type out.
Furthermore, if in your pipe you do need to use the streams ot do some data manipulation, you can access those streams by key, instead of by an array index, without the map operator.
Thanks! I have it in my head that usage is deprecated? I haven't really looked into it though so I'll play around with this and see if I like it better :)
@@JoshuaMorony My comments keep getting deleted if I link the rxjs docs, TH-cam thinks im a spam bot :D
I thought the same, my IDE wrongly keeps suggesting that it is deprecated, while it is not.
The old signature is deprecated, where you pass in multiple sources as multiple arguments. The Array and hte Object signatures are recommended. Same with the ForkJoin operator.
// deprecated
combineLatest(odd$, even$);
// suggested change
combineLatest([odd$, even$]);
// or
combineLatest({odd: odd$, even: even$})
@@Szergej33 Thanks for the clarification! Yeah I get that deprecation warning in the IDE too which is a bit annoying (especially for teaching it)
@@JoshuaMorony You can disable IDE deprecation check and use Es-Lint + eslint-plugin-deprecation instead.
But isn't that the whole CombineLatest operator deprecated, not only this signature ? Replaced with CombineLatestWith, says the doc ?
I absolutely love your channel bro.
Just started working as dev fulltime, grooving into Angular and RxJs with your vidoes.
Big shoutout! ❤✌
So simple, but so valuable. You help me so much with wrapping my head around this stuff. Thank you for keeping you examples relatively simple.
Your videos are always helpful for a beginner like me in Angular.
Awesome. I never knew about ignoreElements, I needed to know something like this existed for work!
I prefer the patten because it is an object it is always true and because the properties are using the async pipe they are null before emitting. Much cleaner in my opinion.
You are losing control over your concurrency as nothing is coordinating those subs. You're putting a lot of TS in the template which is a poor separation of concerns. You're also exposing a lot more of your class internals which promotes coupling. You could literally emit that exact struct from a Subject with a default value.
I think this is worse in every way :shrug:
really liked this approach. I had on of the observables emitting another observable so i usetd startWith(of({}))
Thanks
Another great video Josh !
I find the straight declaration > instanciation very nice, because sometimes TS compiler gives us hard time declaring the proper type when using Observables with all the possible undefined outputs.
Great content, especially in these days when almost everyone claims that angular is dead etc. Keep up the good work! 💪
Thanks for explaining it really well! I've been putting off learning soft soft cuz it looks so intimidating but now that I easily understood the
This is quality content! Glad i found this channel
This is gold. Thanks mate!
I , at this verry moment, will be refactoring a page that receives a separate set of streams from multiple api calls into one stream and then display the a skeleton-text using only one condition. That will be fun. thanks for sharing!
Really nice topic, thank you very much Joshua!
do more of this!! this is amazing
Does using Reactive Forms for large form views change this approach? I generally just subscribe to my data from persisted storage and then set the reactive form data imperatively. At that point Reactive Forms is binding the view and model data.
The *ngrxLet directive is a lifesaver.
learned new things, Very informative.
@Joshua Morony Can you make videos regarding forms and how to handle them in an efficient way, it would be really helpful, Thanks✌.
These videos are outstanding
Hi Joshua! Thankyou for sharing this video. If i may ask, can you sometime covering how to unit test with this kind reactive pattern? Thankyou so much before 🙏
Yes not sure if I have any videos on this already, but a vid on observerSpy (which is what I mostly use to test all my observables) I think would be interesting
Thank you! Your content is very useful, keep uploading videos! :)
Nice pattern, thank you very much!
Thanks josh
Great job man!
This is actually smart
Thanks a lot...very good videos
Awesome video as always
Can u teach how to use nexus
So viewmodel design pattern is basically combine every observable into 1 right?
The view model pattern is more general than just this specific context, but in general means creating some kind of object that has all of the values for your view. In this context, yes I am talking about combining everything into one observable stream that will emit the view model object.
thanks man, good stuff.! One of the best
Love your vids! Keep it up brotha
very useful as always! thanks
It looks very clean but I have a question. When this is better to be used than resolvers? I think it solves the same problem.
I don't really ever use resolvers so maybe there is some interesting patterns I'm missing, but in my mind you would only use a resolver when you need to pre-fetch some data before navigating to the page.
what about efficiency? one stream emission causes whole template rendering
It won't - if you are using OnPush only the value that changes in the vm will trigger change detection where it is used as an input, and if you aren't using OnPush then change detection is triggered for your entire component tree on every change anyway. In both cases though, it's probably important to clarify that Angular won't re-render everything in the DOM anyway, just what has changed.
using `vm$ = combineLatest` in webstorm is not giving me type information about the objects in the template atm... :(
I do the same, rxjs is so awesome
Nice thing but the issue I believe with this approach would be if we use shareReplay for multiple subscriptions then firstly, we will have to trigger subscription again if we want to update data of template after let's say deleting some record. Secondly, we will have to call checkError again. There are solutions to this like a refresh subject in order to forcefully get data again but in the end there will be too many observeables for a basic thing.
Do you have an example of what you mean? There shouldn't be any issues with needing to fetch data again as that's the point of using the observable streams - we aren't pulling data out of the streams to put into our vm, the entire vm is composed of streams that will update when necessary.
Very informative.
Awesome content!
What is the difference between forkJoin and combineLatest?
A forkJoin will wait for all of its input observables to complete and then emit an array containing all of their values - just once. combineLatest will emit every time there are new values, and the input observables may not necessarily complete.
Thanks for the work, ignoreElements are totally new for me. What do you think, what action user needs to take when there is error. Giving option of retry is one solution, is that possible vm$ stream? Or are there better solutios?
Yeah there's a bunch of different things you can implement with streams, you can even implement automatic retries when there is an error (e.g. try making the same request to an API 3 times before finally giving up and erroring)
Great Job! Love your content! Just one question. Would it be better to use BehaviourSubjects with an initial value instead of combining Subjects with startWith operators?
I might be missing your exact meaning but sure if you can set something up as a BehaviorSubject that makes sense, but otherwise if you are dealing with an async stream that doesn't have an initial value then startWith is a good way to deal with that
Hi @Josua ! I really like this approach and it's a really nice pattern to have but don't we need to worry about performance? For example if I have a huge vm object and only one of the subject emmiting one value at some point this will cause the entire vm to emit that huge vm object with only one updated value. In other words, emmiting a entire object only because one subject emmited a value.
This shouldn't effect the rendering of your component because, as far as I understand at least, even if the vm$ is emitting all of the values when any of them change, as long as you are using OnPush it will not trigger change detection for any components using those values as inputs (unless they have changed) as they maintain the same reference.
I think of a use case which could potentially lead us to performance issue if we had some heavy computations inside of our derived vm$, especially if the vm$ itself was large. Thus for each emission inside combineLatest we would have to recompute everything again even though most of the output would evaluate to the same value as the previous one.
Great! 👍
I would like to ask you a question please?
Really cool Rxjs stuff here..... But I wonder, what is the advantage of this approach? It seams that it can get really complicated with a more complex case. If there is no performance improvement, I prefer to keep with more readable code to be easily maintainable. In the real world of software development, this is the key. "Maintainable". I always keep in mind what will happen if in 2 years from now, another developper has to go into this code to fix a bug or add a feature. He should be able to understand how the code is working really fast. And I don't think that kind of advance use of Rxjs is really helping.
At least in my opinion, this makes things simpler. It makes it very explicit what data is intended to be displayed in the template, and there is one mechanism for doing that which will auto update the template as those values change. If you want to display something in the template, you add it to the vm$ stream. I think this provides a nice pattern/rule for many different devs to follow over time, rather than potentially different devs doing different things to display values in the template.
It creates a nice separation of "template" vs "template logic".
Conceptually, I start from the template. "What's do I need to know in order to render this view in all of the states we need to support, with the simplest interface?".
Then I fetch all the things I'll need (usually from API services, user inputs etc) and compose those streams together, finally mapping the data to my simple VM.
It prevents you from adding a property to an API interface somewhere and cascading that down into the template. It also means we can easily keep complex logic out of the template and in the TS instead.
E.g. `*ngIf="user.firstName && user.lastName && user.loaded && user.whatever"` we can just do `*ngIf="user.name"` and move the logic into the observable map().
@@ponderwonder I like your explanation. However, If the vw$ were more complex with more inputs to combineLatest and even more of these derived fields requiring additional pipeable operators to evaluate themselves (such as you described with map to obtain user.name) then I think we would still end up with pretty much the same mess as we never used vm$.
In my opinion, the pattern in the video is very nice though it won't make much difference if we have to deal with bigger number of values inside the vm$. What are your thoughts @Peter Hodges @JoshuaMorony
PS. Thanks for sharing this.
@@valeron_1337 Always welcome the discussion. I think I would argue the opposite to your logic. For very simple components that have a couple of angular inputs and a simple template, using the vm pattern can feel like overkill. And yeah, you can still keep template logic in the TS by using OnChanges to map to an internal property.
As the number of inputs (general inputs, not necessarily angular inputs) increases, and especially where these inputs are asynchronous and can happen over time, the reactive nature of rxjs makes things much simpler. (Think DOM events, socket events, timers, polling etc.)
Without it, it's really easy to get into inconsistent states due to race conditions, things happening in orders you didn't expect etc.
With reactive programming, providing you've piped your streams together correctlt, you'll always get the correct result eventually. If a stream further up the chain emits out of sequence, or twice, unexpectedly, maybe the vm is emitted twice (a mistake) but you'll still end up with the correct result.
The only time I've really seen it become a mess is where people combine reactive programming with imperative patterns (e.g. using a tap and updating a property on the class, or opening a dialog via a function invocation, rather than declaratively) but this is the vm pattern being applied incorrectly rather than a problem with the vm pattern itself.
Josh, excellent video as always! I am curious to hear what you think of the following approach as an alternative to your solution:
In the template, you could use the following condition: *ngIf="{greeting: greeting$ | async, user: user$ | async, count: count$ | async} as vm". I used this approach a few times on my day job
this is bad practice for multiple reasons:
1. make your template as lightweight and stupid as possible. Therefore its easier to change stuff in die future.
2. now you have 3 async pipes in the template vs 1 async pipe. This does not scale in terms of performance and also you're bloating your template what violates Nr1 again. Imagine you have 15 properties with your approach
Still valuable content. Thanks a lot.
You can also use an object in combineLatest if I am not mistaken. Maybe on the latest versions only? No need to map your value anymore.
const fullname$ = combineLatest({
firstname: firstname$,
lastname: lastname$
});
this has been mentioned by dsl dsl one day earlier
@@masterlup Did not notice. Sorry.
You do not answer the question from the title. You just show reasons it is a bad idea.
why are u using angular?
8:10 not gona lie - coming from Vue this looks... horrible. I really want to start liking Angular, but such structures seem over engineered and hard to read.
It looks like solving problems, that we created in the first place. Not really getting the point of doing this.
rxAngular or ngrx component store
Ngrx
What's the main benefit of this? It seems like an unnecessary step for accessing things you may already have access to.
This example is so basic that I dont see any advantage of this approach. I mean with old-style 3x subscribe I gonna have pretty same html and js wont be more complicated either...
And there are some questions, how this code will look like in more advanced, more real cases:
E.g. lets say count$ is an input and user$ is http get with count$ as param -- can u deal this with no subscriptions in js.
E.g. u need to calculate some value based on greeting$ and count$ -- where this code goes? If you add this to combineLatest thing, then its gonna run also when userDelayed$ is emitted.
etc.
Coding reactively is a bit of a different mental model, but the general idea for the scenarios you have suggested is to derive streams in some manner using RxJS operators for whatever you want to add to the vm$ stream. For your count->user example you would have a user$ stream that is derived from the count$ stream and switchMaps to http. For greeting + count you would create a new stream with combineLatest that has both of those streams as input, then use the map operator (or whatever other operator you need) to get the result you want.
All of these streams you create go into the single vm$ stream, and yes any time any of the input streams change the vm$ stream will emit all of the values, but this doesn't matter. If you are using these vm$ stream values as inputs to components the reference is maintained if the value has not actually changed, so you aren't unnecessarily triggering change detection for unchanged values.
biggest mistake in rxjs is error handling
catchError((err) => of (err)) is such common and such ugly solution that it's surprising that no one in rxjs team came up with the idea to donst complete Observale when errors appear.
Observable should be complete only in explicity way using .complete() method. Observable should be stream of errors, and success.
Alternatively inside constructor whe shuld have oportunity to configure that behavior: new Subject({errorCompleteSubject: true})
Too much screen blank or useless code space wasted that could leave place to a bigger font.
Mobile and vision impaired subscribers will be grateful.
Hoax