Тёмный

The mindset you need for a DECLARATIVE code refactor 

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

My Angular course: angularstart.com/
Why thinking like a Karen can help you get in a declarative frame of mind for refactoring a codebase from an imperative style
More on declarative code:
• The easier way to code...
Get weekly content and tips exclusive to my newsletter: mobirony.ck.page/4a331b9076
0:00 Introduction
0:39 Typical imperative approach
1:06 Rule 1: Never change things
2:15 Rule 2: Always speak to the manager
3:30 Refactoring for Rule 1
5:58 Refactoring for Rule 2
Want to build mobile apps with Angular?: ionicstart.com
#angular #declarative #imperative
- More tutorials: modernangular.com
- Follow me on Twitter: / joshuamorony

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

 

11 июн 2024

Поделиться:

Ссылка:

Скачать:

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

Добавить в:

Мой плейлист
Посмотреть позже
Комментарии : 65   
@JoshuaMorony
@JoshuaMorony 3 месяца назад
Join the newsletter: mobirony.ck.page/4a331b9076
@mdfalexis
@mdfalexis 3 месяца назад
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 3 месяца назад
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)
@rosiepone
@rosiepone 3 месяца назад
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 3 месяца назад
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 3 месяца назад
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 2 месяца назад
My entire codebases are spammed with the readonly keyword :D
@jacobadlerman3067
@jacobadlerman3067 3 месяца назад
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 3 месяца назад
Everything you just said about imperative code I see all over the codebases I work with 😅
@mfpears
@mfpears 2 месяца назад
This is so well explained. And it's awesome to see this clicking for people in the comments.
@nathankirsten6555
@nathankirsten6555 3 месяца назад
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.
@JonatanPetersson
@JonatanPetersson 3 месяца назад
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.
@msacket
@msacket 2 месяца назад
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.
@HassanRaza-ym3uf
@HassanRaza-ym3uf 3 месяца назад
Thats a brilliant advice portrayed quite nicely. Thanks Joshua
@andriezel
@andriezel 3 месяца назад
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!
@stoiclunchbox
@stoiclunchbox 3 месяца назад
I finally understand! Thank you!
@sachinkkkkumar
@sachinkkkkumar 2 месяца назад
You're a great teacher❤
@invictuz4803
@invictuz4803 3 месяца назад
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 3 месяца назад
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 3 месяца назад
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!
@johncerpa3782
@johncerpa3782 3 месяца назад
Great video
@AK-vx4dy
@AK-vx4dy 3 месяца назад
Very helpful, but i must rewatch in more calm time to fully grasp whole idea clearly :D
@zero14111990
@zero14111990 3 месяца назад
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
@YouDJeuR
@YouDJeuR 3 месяца назад
same conclusion here !
@kirilldegtyarev6671
@kirilldegtyarev6671 3 месяца назад
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 3 месяца назад
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
@seriouce4832
@seriouce4832 3 месяца назад
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 3 месяца назад
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 3 месяца назад
@@JoshuaMorony Great stuff, this makes a lot of sense. Thank you for your help!
@NoName-1337
@NoName-1337 3 месяца назад
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 3 месяца назад
I will generally prefer to set/next directly in the template
@nazarshvets7501
@nazarshvets7501 3 месяца назад
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 3 месяца назад
I get that you find easier to use the external lib, but i would prefer to see your approach without it.
@JoshuaMorony
@JoshuaMorony 3 месяца назад
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 3 месяца назад
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
@endlacer
@endlacer 3 месяца назад
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 3 месяца назад
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.
@zygas15
@zygas15 3 месяца назад
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 3 месяца назад
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 3 месяца назад
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 ....
@deadlyecho
@deadlyecho 3 месяца назад
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 3 месяца назад
You're correct, it would need destroyRef in this case
@deadlyecho
@deadlyecho 3 месяца назад
​@@JoshuaMorony I thought I was going mad 😂 keep up the great content 🎉
@gnoyl
@gnoyl 3 месяца назад
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 3 месяца назад
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 3 месяца назад
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 3 месяца назад
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 3 месяца назад
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
@Soarex
@Soarex 3 месяца назад
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.
@orlovskyyyy
@orlovskyyyy 3 месяца назад
This is the Karen way
@sFera1001
@sFera1001 3 месяца назад
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 3 месяца назад
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 3 месяца назад
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_ 3 месяца назад
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 3 месяца назад
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 3 месяца назад
IME you end up with the constraints of both and the benefits of neither.
@nazarshvets7501
@nazarshvets7501 3 месяца назад
Great take! Been at the end of each side and I completely agree - best is somethere in the middle
@LarsRyeJeppesen
@LarsRyeJeppesen 2 месяца назад
On the contrary, My Dear
Далее
The easier way to code Angular apps
9:54
Просмотров 64 тыс.
I Asked GPT-4 To Refactor My Legacy Codebase
12:39
Просмотров 348 тыс.
Why Does Scrum Make Programmers HATE Coding?
16:14
Просмотров 490 тыс.
The Story of Next.js
12:13
Просмотров 537 тыс.
Declarative vs Imperative in Functional Programming
17:45
Here's what I've figured out about Angular signals
8:33
Why Neovim nerds are so obsessed with the terminal
6:44
Introducing... the NEW Angular framework
9:16
Просмотров 79 тыс.
Component Input Binding in Angular Router
10:30
Просмотров 8 тыс.