The Angular team at Google kicked off the New Year by releasing Angular 7.2.0 and with that came many great new features. Routing can seem like a pretty simple thing but can easily turn more complicated than expected. For performance reasons, we often want to lazy load modules to optimize our code.
It’s common that we have multiple components that need to resolve some data before rendering them, or also run guards to ensure a user is authenticated or has the correct permissions.
In this post, I’ll go over two of the new features added to the router in Angular 7.2.0:
- Ability to control when Guards and Resolvers are run through function mode
- Allow passing state to NavigationExtra
What's new in Angular?
With the release of Angular 7.2.0, there are quite a few updates to the router. With the exception of the features shown below, there are also numerous bug fixes that I won’t cover in this post.
While the changelog is light on details on how to use them, two of the features that stand out to me, and I will be further exploring in this post are:
- Ability to add Predicate function mode for runGuardsAndResolvers - This will give us the possibility to run a function to determine if guard and resolvers should be run.
- Allow passing state to NavigationExtra - This allows you to provide a state to store in the browser History API.
Controlling when Guards and Resolvers are run
My favourite new feature is the ability to control when Guards and Resolvers are run. We are now able to do this by providing a function to the property runGuardsAndResolvers in the route configuration.
One of the cases where this has been useful is when I’ve had a route with multiple parameters, but only wanting to run the resolver when one of them changes.
Consider the following route configuration - we have shirtId, color and size:
export const routes: Routes = [
{
path: ':shirtId/color/:color/size/:size',
component: ShirtComponent,
resolve: [ShirtResolver],
},
];
When this route is hit, a resolver runs which returns the actual shirt. The API only needs to know the shirtId, and returns all the information you need about the shirt regardless of color and size.
Knowing that we only need to fetch data if the shirtId changes, it does not make sense to run the resolver for color or size. Prior to Angular 7.2.0, this is actually harder than you would expect.
Not anymore, sweet!
Now you can simply provide a predicate function which can contain logic to determine if the guards and resolvers should run or not.
With the new runGuardsAndResolvers feature, we can solve this pretty easily, and I mean VERY easily. Let's break down how this works.
First, we will create the predicate function to be run:
export const predicate: RunGuardsAndResolvers = (
from: ActivatedRouteSnapshot,
to: ActivatedRouteSnapshot,
) => +from.paramMap.get('shirtId') !== +to.paramMap.get('shirtId');
This is the function that we will provide to the router configuration. When Angular executes this function, you will have access to the previous, and next route parameters from the ActivatedRouteSnapShot.
This makes it easy for us to determine if the shirtId has changed between route changes.
Just like that, we’ve created a function that simply checks if the previous route (from) has the same shirtId as the new route (to).
All we need to do next is to utilize this predicate function, is set it to the runGuardsAndResolvers property in the router configuration:
export const routes: Routes = [
{
path: ':shirtId/color/:color/size/:size',
component: ShirtComponent,
resolve: [ShirtResolver],
runGuardsAndResolvers: predicate,
},
];
Without needing to change our path definition, or jumping through hoops in the resolver, we can easily control when it runs or not.
Allow passing state to NavigationExtra
The second new feature is that we are now given the ability to pass a state object to NavigationExtra which will be stored in the History API. This is something we haven’t been able to do with Angular routing before at all.
We can now provide state using either the routerLink directive with a [state] attribute, or programmatically with router.navigateByUrl.
To access the state that you have provided with this new feature, the router has an additional function called getCurrentNavigation which allows you to access the NavigationExtra, and the state within it.
So, how could we potentially use this?
Imagine we have a service that tracks navigation for some analytics.
We want to track more than the URL change and have to provide some additional data. This data can allow us to categorize events, or used to filter and log certain types of navigation.
Previously, we would have to implement some type of lookup table which would map to the URL to get this type of information.
With this new feature, we can now add an extra parameter or object to our NavigationExtra, this means that we could add a trackingId or trackingType.
This can be done in two ways:
Either by the routerLink directive:
<a routerLink="/some-route"
[state]="{ trackingId: 1, trackingType: 'Trackable Link' }">Go to some route with state</a>
Or by the navigateByUrl:
this.router.navigateByUrl('/some-route', {
state: { trackingId: 1, trackingType: 'TrackableLink' },
});
When we have this in place, we can simply have our service listen for navigation changes and using the new getCurrentNavigation() check the state in NavigationExtra.
It’s worth noting that to access the state information for tracking, it is only available during Navigation, and will be set to null when Navigation ends. So to access the state we would have to subscribe to the appropriate event.
this.router.events
.pipe(filter(e => e instanceof NavigationStart))
.subscribe((e: NavigationStart) => {
const {
trackingId,
trackingType,
} = this.router.getCurrentNavigation().extras.state;
// Logic here
});
If we would try to access this after the Navigation completes - getCurrentNavigation() would return null, and we would not have access to the state we provided.
Conclusion
We have some great new functions that allow us to do amazing new things. I highly recommend taking an hour or two to test these new features out and get familiar with them.
- Controlling when Guards and Resolver are run through function mode - My favourite! Allows us to run a predicate function to determine if guards and resolvers should run.
- Allow passing state to NavigationExtra - Allows us to pass in an object to NavigationExtra which will be stored in the History API, great for tracking.
The new predicate function can really enhance performance in your Angular application since you now can decide when to run your resolvers and guards. Making use of passing state to NavigationExtra can be great to increase tracking and analytics on your app. I hope you enjoy these new features as much as I do.
Cheers!