One of the often stated problems with redux is that it contains a lot of boilerplate code, personally I think this is an indicator of redux reducing code complexity, but that’s another story for another time.

One thing is for certain, developer experience is much nicer if one can solve problems by repeating tried and true patterns, as opposed to having to reinvent the wheel each time. Keeping this in mind let’s consider a simple service with code that looks like the following:

const error;

const data;

const loading; function doCall(someId: string) { this.loading = true; this.myService

.doApiCall(someId)

.pipe(

tap(data => this.data = data),

finalize(() => this.loading = false),

catchError(error => this.error = error)

)

}

Now this code is obviously the pseudo ramblings of a mad man, but it’s important to note that the inner operators would be pretty much copy and pasted for each data service in our application. If we had 20 service in our application, that would be 20 copy and pasted segments of code. Therefore even though this code would allow us to complete required functionality, This is not a scalable solution.

Enter the curry function

As described in this article by Eric Elliott, a curry function is

A function that takes multiple arguments one at a time. Curried functions are particularly useful in the context of function composition.

Currying allows us to call a function and then have that function return a new function to the place that called the first function.

const t = (thing) => {

return (other) => other + ' - ' + thing;

} t('one')('two') // "two - one"

In the context of RxJs pipes, this allows us to pass params to a function which will return our rxjs operator. We will use NGXS as an example, and so let’s convert our above code to the NGXS style:

Now lets add a curried util function which will handle the finalize step of our loading…

function finalizeLoading(ctx: StateContext) {

return finalize(() => ctx.patchState({loading: false}))

};

Now we pass our state context to our finalizeLoading function which will return the finalize operator with our state context operator embedded inside, when the stream finalizes it will execute our desired state context.

Now for the error handler, let’s step it up a notch

function handleError(ctx: StateContext, message: string) {

return catchError(error => {

ctx.patchState({ error });

ctx.dispatch(new DisplayErrorNotification(message));

return throwError(error);

})

};

The same pattern applies here, however the difference is that we can even dispatch another action with this pattern, and pass additional params to our inner function. Super powerful.

And so now when we write our action handlers it feels like 😌, we can just insert whichever desired bit of stream functionality wherever we need it, imagine instead of one stream handler we have hundreds.

One interesting thing to note is that our code now looks more like a set of instructions, rather than actual code.

Conclusion

This solution is much more DRY and composable, we can move these functions into a shared location and have all our service call them as needed.

We still have the ability to get intricate in how we handle streams, whilst not having a lot of boilerplate and duplicated code.

We can now create one test for our curry util, and to get coverage for our action handlers we just have to ensure that it calls our util(which is covered by unit tests).

We can even compose our curry functions together, for example we could group our handleError and finalizeLoading into a single genericHandler function, which would reduce our boilerplate even more.

and into a single function, which would reduce our boilerplate even more. The code is self documenting through abstractions, grouped functionality is encapsulated by higher level naming conventions, for example: the GetRowsData action calls the getRowsService , and mapsToEntites on success or notifiesOnError on failure, and upon completion finalizesLoadingStatus .

Thanks for reading, hope you got something out of this, if you want to support my work considering trying my free app link below