Тёмный

The mindset you need for a DECLARATIVE code refactor 

Joshua Morony
Подписаться 78 тыс.
Просмотров 12 тыс.
50% 1

Опубликовано:

 

30 окт 2024

Поделиться:

Ссылка:

Скачать:

Готовим ссылку...

Добавить в:

Мой плейлист
Посмотреть позже
Комментарии : 71   
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
Join the newsletter: mobirony.ck.page/4a331b9076
@mdfalexis
@mdfalexis 7 месяцев назад
A general rule of mine is "make it readonly or const by default." If you have to change the value, you therefore have to think about it's reactivity.
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
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)
@goldensunrayspone
@goldensunrayspone 7 месяцев назад
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
@gnoyl
@gnoyl 7 месяцев назад
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.
@adambickford8720
@adambickford8720 7 месяцев назад
The problem is you can have some kind of const wrapper like an Subject, with `next()` coming from outer space. Its not really any better than mutable.
@tobiasegli6056
@tobiasegli6056 7 месяцев назад
My entire codebases are spammed with the readonly keyword :D
@dasnerft96
@dasnerft96 15 дней назад
omg i love you
@jacobadlerman3067
@jacobadlerman3067 7 месяцев назад
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 :)
@kevinrobertandrews
@kevinrobertandrews 7 месяцев назад
Everything you just said about imperative code I see all over the codebases I work with 😅
@msacket
@msacket 7 месяцев назад
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.
@JonatanPetersson
@JonatanPetersson 7 месяцев назад
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.
@mfpears
@mfpears 7 месяцев назад
This is so well explained. And it's awesome to see this clicking for people in the comments.
@nathankirsten6555
@nathankirsten6555 7 месяцев назад
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.
@andriezel
@andriezel 7 месяцев назад
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!
@invictuz4803
@invictuz4803 7 месяцев назад
I always wondered if I was doing something wrong by calling subject.Next(), relieved to finally hear you can't be 100% declarative.
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
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.
@bric305
@bric305 7 месяцев назад
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!
@stoiclunchbox
@stoiclunchbox 7 месяцев назад
I finally understand! Thank you!
@HassanRaza-ym3uf
@HassanRaza-ym3uf 7 месяцев назад
Thats a brilliant advice portrayed quite nicely. Thanks Joshua
@sachinkkkkumar
@sachinkkkkumar 7 месяцев назад
You're a great teacher❤
@AK-vx4dy
@AK-vx4dy 7 месяцев назад
Very helpful, but i must rewatch in more calm time to fully grasp whole idea clearly :D
@zero14111990
@zero14111990 7 месяцев назад
I had that same situation in a project but in Angular 7 and it was new with its architecture so it was not changed to something declarative XD
@johncerpa3782
@johncerpa3782 7 месяцев назад
Great video
@ghevisartor6005
@ghevisartor6005 2 месяца назад
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.
@JoshuaMorony
@JoshuaMorony 2 месяца назад
Yes services having public observables is fine
@NoName-1337
@NoName-1337 7 месяцев назад
So you would write in the template: or would your write an method for setting the filter value: setFilter(value:string) { this.#filter.set(value); }?
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
I will generally prefer to set/next directly in the template
@kirilldegtyarev6671
@kirilldegtyarev6671 7 месяцев назад
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!
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
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
@deadlyecho
@deadlyecho 7 месяцев назад
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
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
You're correct, it would need destroyRef in this case
@deadlyecho
@deadlyecho 7 месяцев назад
​@@JoshuaMorony I thought I was going mad 😂 keep up the great content 🎉
@4444-c4s
@4444-c4s 4 месяца назад
Instead of using insanely complicated Signals + Rxjs , I would prefer ReactJs over Angular. Angular team is making it more than complex it should be.
@YouDJeuR
@YouDJeuR 7 месяцев назад
same conclusion here !
@endlacer
@endlacer 7 месяцев назад
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?
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
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.
@Soarex
@Soarex 7 месяцев назад
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.
@nazarshvets7501
@nazarshvets7501 7 месяцев назад
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
@pedrofernandes2005
@pedrofernandes2005 7 месяцев назад
I get that you find easier to use the external lib, but i would prefer to see your approach without it.
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
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
@nazarshvets7501
@nazarshvets7501 7 месяцев назад
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
@seriouce4832
@seriouce4832 7 месяцев назад
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"?
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
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.
@seriouce4832
@seriouce4832 7 месяцев назад
@@JoshuaMorony Great stuff, this makes a lot of sense. Thank you for your help!
@gnoyl
@gnoyl 7 месяцев назад
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 ?
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
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)
@JoepKockelkorn
@JoepKockelkorn 7 месяцев назад
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...
@fletcherbond6685
@fletcherbond6685 7 месяцев назад
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.
@nazarshvets7501
@nazarshvets7501 7 месяцев назад
its fine until its overcomplicated. If it doesn't bring your pain then you need to change something in that code, leave it as is
@JoepKockelkorn
@JoepKockelkorn Месяц назад
@@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.
@zygas15
@zygas15 7 месяцев назад
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.
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
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)
@gnoyl
@gnoyl 7 месяцев назад
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 ....
@orlovskyyyy
@orlovskyyyy 7 месяцев назад
This is the Karen way
@sFera1001
@sFera1001 7 месяцев назад
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
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
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)
@adambickford8720
@adambickford8720 7 месяцев назад
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.
@_Greenflag_
@_Greenflag_ 7 месяцев назад
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.
@JoshuaMorony
@JoshuaMorony 7 месяцев назад
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
@adambickford8720
@adambickford8720 7 месяцев назад
IME you end up with the constraints of both and the benefits of neither.
@nazarshvets7501
@nazarshvets7501 7 месяцев назад
Great take! Been at the end of each side and I completely agree - best is somethere in the middle
@LarsRyeJeppesen
@LarsRyeJeppesen 7 месяцев назад
On the contrary, My Dear
Далее
The easier way to code Angular apps
9:54
Просмотров 68 тыс.
ngTemplateOutlet is WAY more useful than I realised
16:36
Angular change detection explained in 5 minutes
6:06
TOP 6 Mistakes in RxJS code
18:35
Просмотров 21 тыс.