Image for post
Image for post
📷 by Annie Spratt

The Angular Router is a very powerful tool and very easy to set up in a regular Angular Project. But how can you use it’s power in your Angular Web Component?

First imagine the following Application Structure:

RootApplication
├── ComponentA
├── ComponentB
├── WebComponents
├── ElementA
│ └── SettingsHome
│ └── SettingsProfile
└── ElementB
└── ElementBComponent

Our index.html looks somewhat like this:

<app-root modules-to-load="element-a, element-b"></app-root><element-a></element-a>
<element-b></element-b>

Our Root Application is responsible for lazy loading our Web Components and populating the Web Component element tags when we need them. It also implements the RoutingModule to route to ComponentA or ComponentB.

In each Web Component we now also want to use the Angular Router, but because they are encapsulated Angular Modules, this is where things get a bit tricky.

Can’t i just use RouterModule.forRoot()?

Since our Root Application is already using the RouterModule forRoot Angular throws an Error.

What aboutRouterModule.forChild()?

Besides, it won’t work, because our Web Components are like small standalone Angular Applications and they don’t know if there is a root RouterModule in a different Angular App.

Can I even use the Angular Router in my Web Components?

Yes and no.

Because the Router modifies the Path of our URL, we would need to handle route changes from multiple outlets across the entire page.

Let’s say on our page https://example.com we route to ComponentA by navigating to the path /component-a
The URL now looks like this: https://example.com/component-a

But what if we also want to route to a component INSIDE a Web Component, e.g. ElementA at the same time?

Right now I think the best solution for this would be to use named router outlets inside the Web Component and append them to the current path. Using this strategy, the URL can include both the root route and our secondary routes: https://example.com/component-a(elementA:settingsHome) (More about this later).

The second option would be to use routing without displaying the route changes in the Browser URL at all. Angular already ships with a great solution for this — the MockLocationStrategy.

MockLocationStrategy is technically only used for testing purposes, but because it fires simulated location events it is also a great solution for our routing problem.

This means when we navigate to SettingsHome inside ElementA, the browser path won’t be changed and still reads the previous URL. https://example.com/component-a

Great! How can I use it?

First set up your routes inside the Web Component, e.g. ElementA and also define a named router outlet inside the root component of ElementA.

element-a-routing.module.ts

const routes = [
{ path: '', component: SettingsHomeComponent, outlet: 'elementA' },
{ path: 'profile', component: SettingsProfileComponent, outlet: 'elementA' },
{ path: '**', redirectTo: '', pathMatch: 'full'}
]

element-a.component.html

<router-outlet name="elementA"></router-outlet>

Then add the RouterTestingModule (it already includes the MockLocationStrategy) to the imports of the ElementARoutingModule and call withRoutes(routes) to set up your routes.

Add RouterTestingModule inside element-a-routing.module.ts

Having done that you need to import the ElementARoutingModule in the ElementAModule.

Import ElementARoutingModule inside element-a.module.ts

Finally call initialnavigation() inside the ElementAComponent and you are ready to go!

Trigger initial navigation inside element-a.component.ts

Now inside ElementA, you can navigate to your routes inside the components by calling:

this.router.navigate([{outlets: {elementA: 'YOUR_ROUTE'}}]);

Or using routerLink in the component html:

<button [routerLink]="[{outlets: {elementA: 'YOUR_ROUTE'}}]"> Go to Settings</button>

To navigate to the empty route use null instead of the name of your route.

Image for post
Image for post

But what if I want to see the the route changes in the Browser URL?

As written earlier this is more complicated and requires modifying some Angular Logic. Here’s a ‘quick and dirty’ solution I came up with, that demonstrates the basic idea behind this strategy:

To handle both external and internal location changes you create a new LocationStrategy based on the PathLocationStrategy, called MultiLocationStrategy (or however you like).

Then you can create our own RouterModule called ElementRoutingModule that provides the MultiLocationStrategy.

Finally, replace the RouterTestingModule with the new ElementRoutingModule.
Refresh your browser and you can see how the URL changes when you navigate to your routes. How cool is that!

Image for post
Image for post
The outlet is appended to the path.

However, there is still a major problem. If you reload the browser after you have navigated to an outlet route, Angular throws a Navigation Error. That’s because it can’t find the outlet inside of the Root Application routes.

Image for post
Image for post
Angular can’t find the outlet route.

To fix this you need to listen to the Route Events inside the Root Application and then do the following on the start of the initial Navigation (NavigationStart):

1. Remove the outlet from the route path
/hostB(elementA:settingsHome) => /hostB

2. Navigate to the route that is available inside the root routes => /hostB

3. Set the location back to the full path
/hostB => /hostB(elementA:settingsHome)

You can implement this logic inside of a HostLocationService, that is called OnInit in the root AppComponent.

constructor(..., private hostLocation: HostLocationService) { }ngOnInit() {
...
this.hostLocation.handleNavigation();
}

Now both routes are correctly mapped to their respective route in the Root Application and in our Web Component ElementA!

Source code

You can find the complete source code of examples on my GitHub repository.
Feel free to play around with everything!

Are there any other options?

Well the easiest option of all would be to just ditch the Router completely and use a state system like NGRX, NGXS, Akita, your own based on RxJS Behavior Subjects or the Angular UI-Router.

Since there isn’t a perfect solution available for routing inside Angular Web Components, it’s still fun to play around and learn something new about the Angular Router 😄

If you found this tutorial helpful please support this article with your 👏 👏

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store