Declarative Route Path Management in Angular Apps — Even Better Than Best Route Forward
Tags: angular, webdev, javascript, programming
Published: 2021-07-12 07:52:50

---
title: Declarative Route Path Management in Angular Apps — Even Better Than Best Route Forward
published: true
date: 2021-07-07 06:49:25 UTC
tags: angular,webdev,javascript,programming
canonical_url: https://medium.com/angular-in-depth/declarative-route-path-management-in-angular-apps-even-better-than-best-route-forward-ee9d7c46fe87
---

![](https://cdn-images-1.medium.com/max/272/1*UUSQoadZ8xjbfibM6_Af1A.png)

### Declarative Route Path Management in Angular Apps — Even Better Than Best Route Forward

When I read [Netanel Basal's](https://medium.com/u/b889ae02aa26) article — “[Best Route Forward — Declarative Route Path Management in Angular Apps](https://netbasal.com/best-route-forward-declarative-route-path-management-in-angular-apps-a44c7e39c340)”— I wanted to try out the solution to route path management described in the article right away in the apps I work on. The solution in Netanel’s article is intended to help with managing routes in large Angular apps. The idea is great! However, I quickly discovered that the solution does not quite work for the apps that have many feature modules with their own routes — i.e large apps. If these feature modules have their own lazy feature modules with their own routes, a single service class really does not cut it. Let me demonstrate what I mean using a simplified example.

Here is an AppModule with the following routes:

{% gist https://gist.github.com/DmitryEfimenko/44b47141013e582b425654f27e8241f1 %}

There are two lazy modules for routes “products” and “customers”. The Products module contains a feature module as well. Here are the associated feature route declarations:

Products routes:

{% gist https://gist.github.com/DmitryEfimenko/619ff462b07a3cebf0479e84e42ae136 %}

EditModule routes:

{% gist https://gist.github.com/DmitryEfimenko/8b738c23d1a98fd4c511f5ee29f0b156 %}

A class with methods, like in Netanel’s article, works great for a flat route structure:

{% gist https://gist.github.com/DmitryEfimenko/11d4261e6847a84993766b6e7a789e2d %}

But what can be done with the routes for the lazy feature module? Below are three naive options that come to mind.

### 🤔 Naive Option #1

Create methods only at the top-level disregarding the nested nature of routes:

{% gist https://gist.github.com/DmitryEfimenko/c62e5cb7553eaa6ee25b4b68d6f0b9db %}

Here’s how it would be used:

{% gist https://gist.github.com/DmitryEfimenko/c09e96d095614eb0564d42083e5f2d40 %}

This approach has some clear downsides:

- The methods for a feature module are managed within the same class.
- The method names are long and repetitive.
- Each child route explicitly specifies the parent /products path.
- This will get really ugly for child routes of the edit feature module.

### 🤔 Naive Option #2

Have the products method return an object to attempt to represent the nested nature of routes:

{% gist https://gist.github.com/DmitryEfimenko/193c7a2bdd23609200a7475805f5aa24 %}

Now, something like this can be typed:

{% gist https://gist.github.com/DmitryEfimenko/8487eb5594bb0e4285ec45f17d145614 %}

This feels a bit better, but there are still a few downsides:

- The methods for a feature module are managed within the same class.
- The root products route is lost.
- Each child route explicitly specifies the parent /products path.

### 🤔 Naive Option #3

Create a separate class for products routes:

{% gist https://gist.github.com/DmitryEfimenko/7ca92f13426ec5d0a7622af8083c27d3 %}

This approach also lets the route be used like so:

{% gist https://gist.github.com/DmitryEfimenko/13194dd7f718ab057ab09c2f3920a6a2 %}

Now, the ability to manage child routes in separate files is gained, but **the ability to use Angular’s dependency injection has been lost**! The following downsides still exist:

- The root products route is lost (could add a method root()?).
- The explicit use of this.parentPath does not feel DRY.
- the parentPath needs knowledge of where it is in the hierarchy of lazy feature routes. Otherwise resulting URL will be wrong.

### 💪 RoutePathBuilder

Long story short, I decided to create a solution that will keep all the benefits of Netanal’s solution and add the features I was looking for:

**Original features**

- A single source of truth for each path in the application
- Strong typings
- Access to Angular’s dependency injection
- Use of absolute links (meaning, the generated link is absolute)

**New features:**

- Managing routes of feature modules via separate classes
- Use of property chaining to reflect the nested nature of the routes
- No explicit use of parentPath in method implementations. Use of relative URL parts for the assembly of the URLs.
- Flexible return type: to access either a `url`, a `urlTree` (useful for RouteGuards), or seamlessly `navigate()` to the desired route
- A utility function to simplify the use of the `this.route.createUrlTree(commands)` method

Say hello to [@ngspot/route-path-builder](https://www.npmjs.com/package/@ngspot/route-path-builder).

The @ngspot/route-path-builder library consists of a single abstract class — `RoutePathBuilder`. Here is how the new library will describe the routes using the hypothetical example from above.

{% gist https://gist.github.com/DmitryEfimenko/d8f737c5e2aef9c6b3780fa52ec81d44.js %}

With this setup, inject the AppRoutes anywhere in the app and use it!

The `url` and `urlFromCommands` methods return an instance of the `AppUrl` class. This class has the `url` and `urlTree` properties and a `navigate()` method. With this in mind, here’s how the AppRoutes service can be used:

{% gist https://gist.github.com/DmitryEfimenko/d4988ef4cf01ecfb9682010a9b433781 %}

Here’s how AppRoutes can be used in a route resolver:

{% gist https://gist.github.com/DmitryEfimenko/f40bcefcc1890ef6943312abdde2803a %}

The RoutePathBuilder provides a `root()` method that returns the AppUrl for the root path of a given feature module. For example:

{% gist https://gist.github.com/DmitryEfimenko/31173f8a5a2f1b16a2eb2538b782c166 %}

The RoutePathBuilder also exposes two protected properties — router and injector. The router is there as a convenient way to access the router in case it is needed without having to inject an extra service in the component or service. The injector is also there to avoid providing dependencies in the constructor. For example:

{% gist https://gist.github.com/DmitryEfimenko/4642a4e68918821ca1e5b607605ee8d0 %}

Of course, dependencies can also be provided in the constructor, but in that case, Injector needs to be added to the dependencies and `super(injector)` added the to the body of the constructor.

Notice the use of { providedIn: 'any' } for the services that extend RoutePathBuilder. This means that a separate instance of that service will be created for each lazy feature module of the app. This is important because the injector should be the reference to the injector of that lazy module, not the injector of the root module. This way, accessing a service declared in the lazy feature module will not fail.

I hope you find the [@ngspot/route-path-builder](https://www.npmjs.com/package/@ngspot/route-path-builder) library helpful. I wish you happy navigating!

#### 👏 **Special thanks to** [**Ana Boca**](https://github.com/anaboca) **for reviewing, testing, and providing some of the code for this article.**

### 🚀 In Case You Missed It

[@ngspot](https://github.com/ngspot) has more goodies! For example, [ngx-errors](https://github.com/ngspot/ngx-errors) — an [Angular-endorsed](https://twitter.com/angular/status/1355259422545752076) library for displaying validation messages in forms. More will be coming soon!

_Follow me on_ [_Medium_](https://medium.com/@dmitrief)_or_ [_Twitter_](https://twitter.com/dmitryaefimenko)_to read more about Angular and JS!_

* * *