Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use 'signalFrom' helper in services #2

Closed

Conversation

jpaju
Copy link

@jpaju jpaju commented Nov 1, 2023

If you'd like to make the state signals more declarative, it can be achieved quite easily with a simple helper function (I named it signalFrom). This way to understand how the value of a state signal is calculated, the only place to look is its definition

The idea seems really similar to #1, where the signalFrom is from the rxState library.

@joshuamorony
Copy link
Owner

Hey thanks for the example! It's funny I also landed on pretty much that exact API too, except I called it "reducerSignal" - the downside is that with the reducers needing to be declared up front you do lose the ability for reducers to make use of existing state. I'm not sure this is a bad thing, it's kind of a good thing really, but definitely a tradeoff. Not sure if I prefer the approach or not yet, I'll have to play around some more.

@jpaju
Copy link
Author

jpaju commented Nov 2, 2023

I think the reducerSignal / signalFrom API is quite obvious when looking for a more declarative API. I'm sure the API could be refined, maybe to enable reducers that access the existing state if that turns out to be useful

@joshuamorony
Copy link
Owner

I've been working on this a little to get it to optionally support any number of reducers. As I mentioned my starting point was almost identical to the API you've suggested here, and for now I've landed on this API for supplying different sources with different reducers:

  state = connectSignal(
    this.initialState,
    this.nextState$,
    [
      this.add$,
      (state, checklist) => ({
        checklists: [...state.checklists, this.addIdToChecklist(checklist)],
      }),
    ],
    [
      this.remove$,
      (state, id) => ({
        checklists: state.checklists.filter((checklist) => checklist.id !== id),
      }),
    ],
    [
      this.edit$,
      (state, update) => ({
        checklists: state.checklists.map((checklist) =>
          checklist.id === update.id
            ? { ...checklist, title: update.data.title }
            : checklist
        ),
      }),
    ]
  );

Basically you can supply as many args as you want after the initial value, and they can either just be observables, or they can be an array containing an observable and a reducer function. I think this API is nice enough to use, though let me know if you had any other ideas.

I've only done a quick prototype but the downside at the moment with this API is that I can't infer the type for the observable values from the reducers. If I use a generic for the observable value it will just use whatever the type was for the first observable/reducer array, but if you supply more than one array it will still use the type value from the first. So I've had to use any for now - not sure if there is a way around this, but my advanced TypeScript knowledge is lacking so there might be something obvious I'm missing.

You can see the prototype here: https://github.com/joshuamorony/angularstart-quicklists/blob/reducerSignal/src/app/shared/utils/connectSignal.ts

Anyway, let me know your thoughts if you have any - I'll run this by Chau to see if there might be any interest in incorporating it into ngxtension

@joshuamorony
Copy link
Owner

Solved the typing issue, which requires using it with this withReducer helper function (not sure I like the naming for this, but it works):

  state = connectSignal(
    this.initialState,
    this.nextState$,
    withReducer(this.add$, (state, checklist) => ({
      checklists: [...state.checklists, this.addIdToChecklist(checklist)],
    })),
    withReducer(this.remove$, (state, id) => ({
      checklists: state.checklists.filter((checklist) => checklist.id !== id),
    })),
    withReducer(this.edit$, (state, update) => ({
      checklists: state.checklists.map((checklist) =>
        checklist.id === update.id
          ? { ...checklist, title: update.data.title }
          : checklist
      ),
    }))
  );

@joshuamorony
Copy link
Owner

So this ended up taking a bit of a different path, but we ended up with something pretty cool that is going to be added to ngxtension if you're interested: ngxtension/ngxtension-platform#135

@jpaju
Copy link
Author

jpaju commented Nov 7, 2023

I think the API you ended up with seems really nice! I had something like that in mind but didn't have the time to refine the API further

@jpaju jpaju closed this Nov 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants