It’s been a while since my last post because I’ve been busy cooking you people some pretty interesting Angular testing learning material.

But today, I read the following tweet:

And it got me inspired to share my pattern of configuring routes in large scale Angular apps.

Since the beginning, when I read the Angular Routing docs, and saw the “Routing Module” pattern suggestion, I was confused.

“Why do I need a separate module for routing?” I asked.

Nobody answered because I was alone. 🙁

But then I said: “What the hell, let’s try it!” 😎

And I started applying this pattern in one of the Angular 2 (yeah it was a while ago) apps I was beginning from scratch.

Some time went by, and the app grew.

To keep things clean we started separating larger modules into smaller nested modules, but then, I started noticing a HUGE shortcoming when it came down to using nested routes.

Problem #1 — Redundant URL Prefixes

Let’s say we have the following folder structure:

app/

app.module.ts

app.routing.module.ts

settings/

settings.module.ts

settings.routing.module.ts

account

account.module.ts

account.routing.module.ts

details

details.module.ts

details.routing.module.ts

Take details.routing.module.ts for example.

With the “Routing Module” approach we always begin our route URLs from the base URL.

So every route in details.routing.module.ts will need to have the previous routes URLs (“settings/account” ) as its prefix:

details.routing.module.ts

~~~~~~~~~~~~~~~~~~~~~~~~~ @NgModule({ imports: [ RouterModule.forChild([ {

path: 'settings/account/details',

component: DetailsComponent

},

{

path: 'settings/account/details/some-other-route',

component: SomeOtherComponent

} ]) ],

declarations: [DetailsComponent, SomeOtherComponent] exports: [RouterModule] }) export class DetailsRoutingModule{ }

“Why is that a problem Shai?”

BECAUSE…

for medium to large size apps, these routes will repeat themselves over and over again.

It could slow us down if we ever need to refactor parts of the apps which involve the routes.

We cannot just “plug” a module on top of a different module and expect it to work…

And if you’re thinking “but I can’t just move modules around anyway, it will break my app…”

I got 2 things to say:

Router testing — which I’ll talk about in the future. Variable based routerLinks — which I’ll talk about later in this post.

Problem #2— Lazy Loading

If for example, we needed to turn details.module.ts into a lazy loaded module, we would have to go ahead and remove all those prefixes from all the details’ routes.

details.routing.module.ts (LAZY)

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @NgModule({ imports: [ RouterModule.forChild([

{

path: 'details', // <-- no more prefix

component: DetailsComponent

}, {

path: 'details/some-other-route',

component: SomeOtherComponent

}

])

],

declarations: [DetailsComponent, SomeOtherComponent] exports: [RouterModule]

}) export class DetailsRoutingModule{ }

Why? because routes of lazy loaded modules become the children of the parent loading route.

WAT?

Yeah, you can imagine the child routes of the lazy loaded module “stacking” on top of the parent loading route.

By the way, that’s why we use loadChildren to load lazy loaded modules, as if to say: “Set this sucker’s routes as the loading route’s children”

“Again, why is that a problem Shai?”

In one word: inconsistency.

( In two words: in-consistency 👏 👏 👏 “good job Shai!”)

When we scale up our apps, we want things to be consistent.

We want to reduce the amount of decisions we need to make, so every inconsistency creates unnecessary noise.

“Should I remove the prefix here or leave it? why can’t it just be the same as the other routes…?”

We want to reduce these 👆 questions.

Demo Project To Show The Problem:

Here is an example project that I created for you to see what I’m talking about: