Why should you consider using the query parameters?
There should be always the only and preferred source of truth for some data. In many applications it is a store (denormalized state of the application), in some cases, it is very convenient to have another “higher” source of truth for some data if that source is more likely to be kept up to date and may be used for more appliances. Query parameters are an example of such useful data. If the website uses query params to store some data it usually means that thay are either needed for initialization (prefilling data like an email in the form) or that are meant to be consistent on the same URL and “shareable” by that URL. A good example is filtering for over some data.
Let’s have a look at this (familiar) URL:
google.com/search?q=text+to+search
It will perform a search using the search query string "text to search" specified in the q param. This is consistent and expected. Similarly, you as a user expect that if you copy an URL of an e-shop with all the filters set for all the things you desire in a product, that search should be the same in the scope of filers only performed on the latest product set with up to date results.
Query params handling strategy in navigation
There are three ways in how query parameters will behave when performing changes. With their differences in mind, you may have a pretty clear strategy on when they should be used.
Merge
Combines (merges) the previous set of values with the current set.
Ideal for updates like changing the pagination or filter.
Here are some examples of usage:
In this situation, you want to merge your params with anything that already might be in the route. Let’s say you have a list of data that has pagination in place, ordering maybe also some filters. You’d like to offer your user a choice of multiple view modes (like some email clients do). You want to keep all the previously set params (page, ordering, …) but also set the viewMode. This is a pretty straight forward way to go.
<a
routerLink="."
queryParamsHandling="merge"
[queryParams]="{
viewMode: 'compact'
}"
>Enable compact view mode</a>
or alternatively
router.navigate(['.'], {
queryParamsHandling: 'merge',
queryParams: { viewMode: 'compact' },
});
Pre-navigation: /my-list?orderBy=name&page=2.
Post-navigation: /my-list?orderBy=name&page=2&viewMode=compact.
A different scenario comes to mind when you’d like to add some parameter that acts as a condition of a sort, let’s say filter for some specific category. In that case, it seems pretty irrelevant that we are currently on page 2, we don’t want to stay on that page anymore if we’d like to see all data in a specific category.
This is achievable by setting the corresponding parameter to undefined.
<a
routerLink="."
queryParamsHandling="merge"
[queryParams]="{
category: 'Angular',
page: undefined
}"
>Articles about Angular</a>
or alternatively
router.navigate(['.'], {
queryParamsHandling: 'merge',
queryParams: {
category: 'Angular',
page: undefined,
},
});
Pre-navigation: /my-list?page=2.
Post-navigation: /my-list?category=Angular.
Preserve
Keeps all query params in route the same.
It will ignore the new ones.
Very handy if you need to change a route but you want to keep all the query params.
An example would be a locale abbreviated route like this /en/article?id=42 where you'd like to change the language with single navigation to /fr/article?id=42.
<a
routerLink="/fr/article"
queryParamsHandling="preserve"
></a>
or alternatively
router.navigate(['/fr/article'], { queryParamsHandling: 'preserve' });
Default
Replaces all the params with new ones.
This option is used when no strategy is provided.
Ideal when you want to set params to precisely the state you are providing with no intervention with what already might be set.
Used when changing route completely with no intention to keep the query params belonging to the previous route.
There are use cases to use all individual strategies if you’d like to know more about the tech side of in-template navigation, see RouterLink directive.
Using the query params values
The angular router provides us with the ActivatedRoute which you can easily use after adding it to your constructor parameters. In most cases, you’ll need to access the data via the observable stream.
People sometimes use the snapshot access data right away but as tempting as it might be to access query parameter values without a need of subscribing, but we are performing the same page navigation when changing query parameters without changing the route path and that will not be reflected in the snapshot. In other words, you need to either subscribe or even better use the async pipe with the in-template variable declaration.
Let’s have a look at an example of how you’d fetch data (in our case list of games) based on the parameters in the query part of the route. In the example below, you can see pagination, sort (ordering) and filter (genres).
({ /* ... */ })
export class DemoComponent {
constructor(
route: ActivatedRoute,
games: GamesService,
) {
route.queryParams.subscribe(
({ page, pageSize, ordering, genres }) =>
games.fetchGames({
pageSize: pageSize || PAGE_SIZE,
page: page || 1,
...(genres && { genres }),
...(ordering && { ordering }),
}),
);
}
}
TODO
Router active class behaviour
Links and resources
Conclusion
User Experience (UX)
As route becomes the main source of truth (data/options) for the page, the UX and usability of the site are improved significantly. Users can rely on the same URL showing the same context, links can be shared easily, browser forward back navigation behaviour mirrors the user activity.
Development Experience (DX)
Having all state necessary for a specific route is something that has a very pleasant developer experience for you when you have to create or replicate some functionality. The best thing about it is that it works very well with the hot reload that happens when you serve the app locally. Imagine working on implementing the logic of the filters over some table and having to manually set the values every time the app refreshes. If it is in the route it will stay there and get picked by according initializer for example form builder.
Connect your NgRx store with the Angular router
I’ve prepared an article explaining how to connect your NgRx store with your routing. This will give you better control over how you derive a state based on the route.
// TODO: link the article



