Strongly typing your Angular route data
Use typed route parameters to improve the quality of your Angular code
As Angular projects grow, it’s quite common to add data to your routes. Some of that data will be mandatory for all the routes, some not. But in any case, it’s useful to strongly type your routes, just like the rest of your codebase.
In this article, I’ll quickly go through the base data types of the Angular router, then we’ll explore how to strongly type our Angular routes.
Angular Router API data types
The Angular router API provides us with multiple data types. Here’s a real quick recap.
The most important type is of course the Route. A Route object defines the configuration for a single route, which may include (among other things):
- a path
- a component
- a target router outlet
- guards (canActivate, canDeactivate, etc)
- resolvers (forget those if you can!)
- children routes
- data
- …
If you’re familiar with the Angular router, then you probably know all of this by heart.
The data property is the one you can use to embed additional data, that will be made available by the router as part of the ActivatedRoute. The ActivatedRoute can easily be injected in your components or services (e.g., through firstChild?.routeConfig?.data
); I’ve leveraged this feature in the Scrolling service that I’ve written about recently.
Of course you’ll almost always have more than a single route in your applications. All of those routes need to be added to a Routes object, which is nothing more than an array of Route objects (the type is simply defined as type Routes = Route[]
).
Once your Routes object is defined, you simply provide it to the RouterModule; either through RouterModule.forRoot
or RouterModule.forChild
, depending on whether you’re in the root or in a lazy-loaded / child module.
Once the router configuration is defined, then you’re good to go. When the Angular router needs to activate a different route, it tries to match the destination URL’s segments with the different Route objects it has been fed with. But this is not the subject of this article, so we’ll stop here.
Now that we’ve reviewed the base types, let’s introduce our own!
Strongly typing your route definitions
To strongly type our Angular route definitions, we can extend the base types discussed in the previous section.
What’s interesting to explore is the type of the “data” property. By default, it is defined as follows:
type Data = { [name: string]: any; };
As you can see, it’s a simple record (i.e., dictionary) with string keys and whatever as values. To improve upon this, we can simply extend this base type. Here’s an example:
With the above, I’ve extended the default type provided by Angular with additional properties; one being mandatory.
By using this interface instead of the default one, we can have more help from the TS compiler already. In my project though I went further and didn’t even extend the default Data
type. It’s up to you to be super strict… or not.
Now, how do we use this custom interface? Well we also have to extend the Route
type of Angular:
We’re almost there. This time we’ve just replaced the default data
property of the Route
interface with our own. Next up is indeed the Routes
type:
export type CustomRoutes = CustomRoute[];
Now that we have it, we can use it to define our route arrays in the root and child/lazy-loaded modules:
As you can see, there’s nothing complicated with all this and the benefits are rather obvious:
- Clear expectations
- Standardization
- Fool-proof
Conclusion
In this article, I’ve quickly reviewed the major data types of the Angular router and have shown you how to strongly type your route definitions.
If you take time to do this, then the compiler will help you out whenever you forget something or need to refactor router configuration.
Gotta love TypeScript! ;-)
That's it for today! ✨