Dealing with query params can be some muddy stuff. Done wrong it can be just as ugly as caching logic that is bloating your code base. The way to handle such cross-cutting concerns is using aspect-oriented programming.
Nice namedrop smart ass, what does this mean?
Well, it means that you are coding in a way that allows for separation of cross-cutting concerns, such as eg. caching, logging and… wait for it… query param handling, which is what we will be working with today. In normal words, aspect-oriented programming allows for extending the codebase without actually modifying the code itself. This is done by hooking into specific events in the framework and trigger the desired logic on these events, eg. update query params when some Redux store property changes and update Redux store property when query param changes.
This is how we will implement query param handling by creating automatic synchronization between query param values and a corresponding property in the Redux store.
Using query params vs route params
When passing data from the URL to the app, there are two ways to do it: with query params and route params. Query params are what comes after the “?” in the URL and are key-value pairs separated with “&”. Routes, on the other hand, are a specific place in the URL, where parameters will be injected into the code.
When should you use what?
As a general rule of thumb, you should use query params when you are dealing with optional values and route params when values are mandatory (meaning the page will not make sense without those value. This is only a rule of thumb, as you might prefer to using query params for mandatory values for easier seeing what key the value belongs to and when dealing with many mandatory values. Otherwise, route params can be hard to manage when passing eg. 10 variables to the app through the URL, because it will be very confusing to grasp what keys the values belong to and maintain the order of the values.
Implementing query param synchronization
First of all, we need to set up an Angular app with Redux. This can be done by simply cloning my Redux starter repo, which gives you a nicely structured starter template for redux apps using the Angular style guide best practices.
We handle query params using an adapter for having a nice and easy interface dealing with query params.
Now we have a service for getting query params using an easy interface. As I have written about before this is an adapter to handling query params, so the app is not directly interacting with the Angular router API but instead a simple high-level facade.
Next step is to use this in the Redux Query param sync service:
The query param sync service operates with query param sync objects which are encapsulated logic of how the query param should be synced with a specific action or method.
The property selectorToUpdateQueryParam$ is a store selection observable. When this emits the query param is updated.
The property selectorToUpdateQueryParam$ is a method/action that is fired every time the query param changes.
The query key is the actual query param key displayed in the URL.
The query param synchronization service is started with startSyncQueryParams where the synced query param objects are passed in. From here subscriptions for every query param object will be set up with the selector observable, the method to be triggered on query change and query key, ensuring proper synchronization between Redux store and query param.
You want to be able to add query param sync objects at runtime, which is what addSyncedQueryParam enables. This will stop the current query param synchronization and add the new query param sync object to the synced query params and start query param sync again. This is handy when you want to sync a query param that belongs to a feature module. This can be set up in onInit of the feature component.
Likewise, you can clean up query params using unsyncQueryParam, which will unsubscribe the given query param. This can be set up in onDestroy in a feature module.
The Redux query param sync service will start syncing when you invoke startSyncQueryParams like this:
We now know why to cross-cutting concerns such as caching, logging and query param handling should be implemented in a way that allows extending the codebase without actually modifying the code, called aspect-oriented programming. when its smart to use query params vs router params to pass data from the URL and how to handle query params aspect-oriented using a Redux query param sync service.