Agreed, "readonly or const by default" would probably be a more succinct phrasing of "never change things" but I had to lean into the analogy a bit ;) I actually don't generally use readonly, I just don't reassign things - I don't specifically have anything against it but I'm not sure it's worth the clutter (open to having my mind changed on that though)
I abuse readonly and getter-only fields constantly because otherwise I'm stupid and I'll start changing things which absolutely don't need to be changed
i used to do that then i had to make some tests and it was really hard to change those readonly properties. I usually dont have logic in constructors , instead in ngOnInit so i could just set the values and call ngOnInit for every test.
God bless you, man! God, how I suffered with imperative code! I recently developed a custom stepper and 72 lines of (imperative) code turned into 14! Carl, at 14! Not to mention the fact that the logic itself has become MUCH more compact and clearer at first glance. I can't get enough of this approach. I've searched for this knowledge for years. Thanks again bro :)
Good description of declarative vs imperative code. When I first started working with Angular around version 8, all the examples used RxJS's subscribe. For some reason I can't remember, I concluded (or was told) that using subscribe inside a component was just not a good idea. As a result, my code naturally became declarative using RxJS and the async pipe.
Thank you, super relatable. I'm also working in a mostly imperative angular codebase. Making it more declarative would make me and my colleagues 9-5 so much easier. Will share this with them.
Love your stuff. I have already refactored one app at my company and about to do the exact same thing to another app. It's been a struggle getting the other developers to think declaratively instead of imperatively. They have been doing the imperative style of angular for so long.
Wow. That was confronting to say the least. Not because of the name Karen (which is not my name) but because you perfectly described my imperative way of coding. I just realised that I really need to refactor my applications now. I thank you very much for this video!
Yes absolutely it is going to be required, but also keep in mind that it's sort of an escape hatch too - sometimes you do legitimately need it and it is the right thing to do (handling user interactions is a prime example), but it's also possible to use it in situations where perhaps some data would have been better derived in some other way. So over utilising it will basically just lead to what is essentially imperative code with extra steps.
Great content as usual. I really liked this computedAsync method from ngxtension, I didn't hear about it before and apparently there are many other functions for signals (obviously I knew about the signalSlice that you created)! Maybe a video about them could be nice!
Should services have public observables? I'm trying to refactor some angular code, i usually do Blazor. Parent and children components declare properties declaratively like you explained and those call other public observables from services, these observables are also combined inside their service to make some other computed state property. And is design to have a state that rapresent basically the content read from a file. Now with the refactor i need to have multiple files :) quite a challenge.
So, for "Refactoring for Rule 2" if we want to change the filtration mechanism in RxJS way, we just need to make that "filter" as a Subject and combineLatest it with a data$ and use filter value in a map operator for the incoming data? And of course, thanks for your videos, Joshua! They are really helpful!
It doesn't necessarily have to be a Subject/combineLatest but generally yes that is the idea, the "filter" is some kind of reactive entity in your application, and "filteredData" is another reactive entity in your application that reacts to either the "data" reactive entity or "filter" reactive entity changing
1:39 is this code right ? I thought we couldn't call takeUntilDestroyed without a destroyRef of the component when we are outside the injection context
1. how would i declare flags like isLoading, hasTechnicalError or hasOtherError upfront. My "payload" (data) is mostly declarative, but for flags i mostly resort to updating them imperatively (in case for loading in a the tap and finalize operators of an rxjs stream for example). 2. error handling: how to open modal x on error code 403 and hide (showFlag=true) something on lets say 404. how can this stuff be declared upfront? those things have to be set/executed in a catchError in the rxjs stream, dont they?
It is possible for example to have a separate "error stream" that is derived from whatever it is you are loading that uses catchError + ignoreElements to make that a stream of any errors from some other thing, but personally I prefer a slightly more imperative approach with signals here. This video goes more into how I utilise the "imperative gap" between RxJS and signals: ru-vid.com/video/%D0%B2%D0%B8%D0%B4%D0%B5%D0%BE-R4Ff2bPiWh4.html But essentially what I will do in these situations, say if I am authenticating a user or something, I will have my "userAuth$" stream or whatever, which will end up setting the status (e.g. "authenticating" | "success" | "error") into a signal (typically via connect form ngxtension to avoid the manual subscribe, or you can also just manual subscribe). Then if I wanted to launch a modal on error I would launch that as a side effect of the "status" signal changing to "error" via a signal effect.
Try to have a single source of truth, I see similar patterns in React / Database schema modeling. Track the minimal amount of state (reasonably) possible. Everything else should be derived from that state. You know you have multiple sources of truth when you need to work to keep them in sync.
I expected to see here AbstractConfigBuilderFactoryFactory. I saw (in result) generic, and from my point of view, imperative code, just with several small abstractions for reactivity And I kinda confused. Because result code, is my limit where declarative goes. Because it only works if code doesn't change (same with abstraction hierarchy). Once I did went full and beyond with declarative approach and it was horrible. Because you can't predict which features would be requested, and you can't put all things behind feature flag. I created my own k8s-like config(to describe my whole application) and after that I understand,that its time to refactor it to imperative. The thing is, after refacting, there was less amount of code. And it was way easier to support and extend. I hate code-as-config! On the other side, Tom is a genius
Which part, the computedAsync/createNotifier? I wouldn't do the promise/signals approach without it, computedAsync is basically just hiding RxJS behind the scenes so without it I would just be using RxJS In any case, I did end up deciding not to go with that approach for this particular refactor - it was working but was feeling inflexible and perhaps a bit too risky to take on at this stage
Its not that. It easier to use, because it already has documentation. It already has been covered by test. It already battle-tested by other developers. And the last, but most important - it doesn't require your time to maintain and release new versions! Treat yourself of "Not inverted here" syndrome, it only will limit you in long run
This is really interesting, thank you! I have a simple question though: How would I go about creating a button that toggles the state this.activated from true to false and vise versa? I don't see myself using pipe here, how can this go hand in hand with "never change things"?
If "activated" is toggled via a user interaction then this is a data source and is where you would have to imperatively next a subject or set a signal, this is a thing that is at the "top" of the data flow but anything below that is derived declaratively. However, if "activated" was determined some other way that could be determined from a source (not a user interaction), e.g. some data loading in or something like that, then the "activated" would be derived declaratively from that. "Fully" declarative just isn't really feasible with Angular, but we can get close enough, and certainly to an extent where we get the majority of the benefits.
Every time i reach the same issue. I have simple page with table and CRUD operations. I want to re-fetch the data after all 3 CRUD operations so that we always work with latest data. Imperatively i would call this.getTableData() in every .subscribe() of the 3 CRUDS. Declaratively i would have triggerLoad$ subject that i would .next(true) in all 3 .subscribe of the CRUD, that is also imperative. Do you see a way to improve that ?
At a glance (and obviously without context) I would say that the imperative triggerLoad$.next shouldn't be required - the situation is that you have something (your table data) that wants to react when any of three other streams emit (i.e. they finished whatever operation they were doing). So probably you would want something like tableData$ = combineLatest(thingOne, thingTwo, thingThree).pipe(switchMap to fetching table data)
In every JS framework it's either a manual 'invalidation' action (similar to your triggerLoad$ stream) or a simple navigation that triggers stuff to reload. I don't see any way around that, unless you use websockets or server-sent events to invert data fetching responsibility to your database. So it doesn't get any simpler than what you already are doing...
One idea would be to not refresh the data completely after a crud operation. Instead you could manage the state locally by using the response from the API. For example if you delete an item from the table you could filter it out in the local state after you received a success state from the API. If you create or update an item you could also add or replace it to the local state with the response from the API.
@@fletcherbond6685 This is a very common approach, but it assumes that the user is the only one mutating the data shown. Essentially it's always a tradeoff: more complex code to sync server state to client state but less risk that stale data is shown or less complex code but more risk stale data is shown.
What about side effects in computed? I would like to use declarative approche with signals but sometimes i have other login in tap like changing isLoading flag. But i se that compute is only for transform source value.
Personally I still like RxJS for these sorts of situations, I have 'initial' | 'authenticating' | 'authenticated' | 'error' states some state signal, trigger auth by nexting some observable, and then have some kind of "userAuth$" observable that handles the auth and returns the correct state that then gets set (via connect from ngxtension) into a state signal. Enea is doing some interesting things with createResource though: github.com/nartc/ngxtension-platform/pull/282/files and using Angular Query is also an option here too. But ultimately if you want to tap to set some "isLoading" state I don't think it's that big of a deal (but no you probably shouldn't use computed for side effects)
export class LoadingService { private static _pendingRequestCount = new BehaviorSubject(0); public isLoading$ = LoadingService._pendingRequestCount.asObservable().pipe(map(count => !!count)); public static showLoadingUntilComplete(obs$: Observable): Observable { return of(EMPTY).pipe( tap(() => this._pendingRequestCount.next(this._pendingRequestCount.value + 1)), concatMap(() => obs$), finalize(() => this._pendingRequestCount.next(this._pendingRequestCount.value - 1))); } } You can wrap every HTTP call in LoadingService.showLoadingUntilComplete. The reason it is static is because i've created a Decorator that i place on top of http methods @ShowLoadingSpinner() public getConditionsData(): Observable ....
unfortunately this style isnt usefull in cases, there u want mutate your data on any events, for example lazy-loading nodes-tree, if u want to load node's children on node expand in template. seems like there's no way to do this logic without any "gaps" in declarative style
Do you have an example you could share? I'd be interested to see how I might approach the situation, but yes there will always be imperative gaps (as there are multiple in the video)
That's just not true. You have something that is the source of the event, say your tree node. It would be an observable of some kind that emits its open/close state as it's interacted with. Some downstream thing can react/.pipe() to the open/close signal, potentially combined with other sources. Imagine some king of zip/combineLatest between this tree node and a text box. When either changes you can decide to make/cancel some server side request. In turn, things like spinners and a downstream in a template can consume *that* as *it* changes. All async.
Doing everything declaratively => your application will be hell of complexity, and possibly even not maintainable because of that. Do imperative coding where and when you need it, and do declarative where and when you will need it. There is no ideology in programming. Declarations such as "you should always do that" or "you should never do that" are almost always wrong. Coding is using the right tools at the right places.
I disagree in general, a mix of declarative and imperative code with no real strict rules/guidelines being adhered to is likely going to be a worse situation. Yes, rules are made to be broken, but it's not ideological/dogmatism to follow rules in a codebase like "class members must be readonly/declarative" there might arise situations where that rule would be broken, but it would come with consideration/justification. In general, my preference (in order) would be: 1) as much declarative code as possible 2) all imperative code 3) an adhoc mix of both