The mindset you need for a DECLARATIVE code refactor

แชร์
ฝัง
  • เผยแพร่เมื่อ 27 มิ.ย. 2024
  • 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

ความคิดเห็น • 65

  • @JoshuaMorony
    @JoshuaMorony  3 หลายเดือนก่อน +1

    Join the newsletter: mobirony.ck.page/4a331b9076

  • @mdfalexis
    @mdfalexis 3 หลายเดือนก่อน +24

    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 หลายเดือนก่อน +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)

    • @rosiepone
      @rosiepone 3 หลายเดือนก่อน +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 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 หลายเดือนก่อน +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 3 หลายเดือนก่อน

      My entire codebases are spammed with the readonly keyword :D

  • @jacobadlerman3067
    @jacobadlerman3067 3 หลายเดือนก่อน +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 หลายเดือนก่อน +5

    Everything you just said about imperative code I see all over the codebases I work with 😅

  • @nathankirsten6555
    @nathankirsten6555 3 หลายเดือนก่อน +1

    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 3 หลายเดือนก่อน

    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

  • @stoiclunchbox
    @stoiclunchbox 3 หลายเดือนก่อน

    I finally understand! Thank you!

  • @bric305
    @bric305 3 หลายเดือนก่อน +2

    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!

  • @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!

  • @sachinkkkkumar
    @sachinkkkkumar 3 หลายเดือนก่อน

    You're a great teacher❤

  • @invictuz4803
    @invictuz4803 3 หลายเดือนก่อน +1

    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 หลายเดือนก่อน +1

      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.

  • @mfpears
    @mfpears 3 หลายเดือนก่อน

    This is so well explained. And it's awesome to see this clicking for people in the comments.

  • @AK-vx4dy
    @AK-vx4dy 3 หลายเดือนก่อน +1

    Very helpful, but i must rewatch in more calm time to fully grasp whole idea clearly :D

  • @johncerpa3782
    @johncerpa3782 3 หลายเดือนก่อน

    Great video

  • @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 หลายเดือนก่อน +1

      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 หลายเดือนก่อน +1

    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

  • @pedrofernandes2005
    @pedrofernandes2005 3 หลายเดือนก่อน +2

    I get that you find easier to use the external lib, but i would prefer to see your approach without it.

    • @JoshuaMorony
      @JoshuaMorony  3 หลายเดือนก่อน +1

      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

  • @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 ....

  • @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 หลายเดือนก่อน +1

      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: th-cam.com/video/R4Ff2bPiWh4/w-d-xo.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.

  • @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

  • @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 หลายเดือนก่อน +1

      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 หลายเดือนก่อน +1

    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 หลายเดือนก่อน +1

    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 หลายเดือนก่อน +4

    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 หลายเดือนก่อน +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 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 3 หลายเดือนก่อน

      On the contrary, My Dear