Angular, Module Federation, and remote internal routing 🤯🤯🤯

juliegladden

Julie Gladden

Posted on February 16, 2023

Angular, Module Federation, and remote internal routing 🤯🤯🤯

Hey folks,

There's tons of content out there for Angular with Webpack's Module Federation, specifically for SPAs. Because, if we are being totally honest with ourselves, that's how it was designed to be. 🧠🧠🧠

However, we are just going to conveniently ignore that fact and have microfrontends that aren't quite so... micro. Besides, there's no one here to stop us, so let's do some things that maybe we shouldn't.

First things first, this article is going to assume you've done the hard work of setting up your Shell and your Remote (or Remotes).

Start with our routing in the shell, which lazy loads our remote. This should have been covered in any random tutorial on the internet.


shellRoutes = [
  {
    path: 'myRemote',
    loadChildren: () => 
        import('myRemoteApp/Module').then((m) => m.RouterModule)
  }
]

//In our imports array...
RouterModule.forRoot(shellRoutes, { useHash: true})

Enter fullscreen mode Exit fullscreen mode

Then, in our remote


remoteRoutes = [
  {
    path: '', 
    component: MyHomeComponent
  },
  {
    path: 'list/:id', 
    component: MyRandomListComponent
  }
]

/* Also, be aware that when you use RouterModule, for the 
components in your remote to be rendered in the <router-outlet> 
in your shell, you must use RouterModule.forChild() */

//In imports array
RouterModule.forChild(remoteRoutes)

Enter fullscreen mode Exit fullscreen mode

Now, we would assume that we can just use routing as normal from our remote. However, trying
this.router.navigate(['list/1'])
in our remote will cause issues.

What happens is instead of just navigating directly to that component, it replaces everything that was already in the url after our hash.

For example, instead of getting:

localhost:3000/#/myRemote/list/1

We end up with:

localhost:3000/#/list/1

So now, our shell's router doesn't know to be looking inside of the remote project for its paths.

Your remote doesn't know what path was set by the shell, since they don't communicate with each other, so it just overwrites and starts fresh with it's own path. Even if you do
this.router.navigate(['myRemote/list/1']), it won't work.
Since the remote doesn't speak with the shell, the shell can't find that remote internal path. The shell doesn't even realize you are trying to change paths.

Here's the workaround. Have the shell do the all the routing.
To do this, we are going to utilize a custom event, and an event listener.

In our remote:


myNavigationFunction() {
  const routeChangeEvent = new CustomEvent('childRouteChanged', {
    data: {
      remoteName: 'myRemote',
      routeName: 'myRemote/list' + this.id 
   },
 });

window.dispatchEvent(routeChangeEvent);

/* You may or may not need the data for remoteName, but it can be handy to have, so I pass it anyways. */

Enter fullscreen mode Exit fullscreen mode

In our shell:


/* Listens for the remote to fire its event, and on firing, fires the function 'changeRoute()' */

@HostListener('window.childRouteChanged', ['&event'])
changeRoute(event) {
  this.router.navigate([event.data.routeName])
}

Enter fullscreen mode Exit fullscreen mode

And there you go, you should have a functioning shell and remote relationship that allows you to trigger routing from your remote.

Hope you enjoyed this article,
Julie

💖 💪 🙅 🚩
juliegladden
Julie Gladden

Posted on February 16, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related