Here’s the problem: Trying to get two distinct Angular applications onto a single HTML page. When you try to do this, you’ll notice that your second application will not render.
I know most of you Angular gurus out there are asking why anyone would want to do this. Angular is an enterprise framework meant for building SPAs, not individual widgets. I definitely agree that SPA design is where Angular shines, but not all businesses are able to follow such a pattern, or are upgrading to a SPA and need to be able to build out individual widgets to replace existing legacy widgets until they can fully upgrade to a SPA.
So that being the case, let’s take a look at what happens if you throw two apps on a single page:
As you can see, only one app renders (Whichever app’s scripts come first in the HTML). So how do we get around this?
One solution that you can find online to make this work is to simply wrap each of your Angular Applications in an iFrame. If you’re like me and you see the solution is an iFrame, you probably shudder (don’t worry; I will go over a nicer solution).
An iFrame technically works, because each app is running in its own sandboxed environment and therefore doesn’t cause issues with any other application. For simple apps, this can work, but when things get more complicated and you need inputs in or out of your application, this can become more of an issue. Your application also does not have access to the routes on the parent windows, and ends up a communication nightmare.
Use Angular Elements. If you have not used Angular Elements, they are a fantastic tool that allows you to compile Angular applications into Web Components. This allows for a more native JS/HTML approach. I will not go into how to create Angular Elements in this article, but there lots of great resources out there on that topic.
We used this approach for awhile and it worked great… until it didn’t. The issue became upgrades. If App 1 upgraded Angular or another dependency that App 2 relied upon, and deployed before the other app upgrades, and there was a breaking change, then one or both of the apps would just stop working on the page.
This is a big issue because it only occurs at run time and there is no way to know if one App has a breaking change until you throw both on a page. This made our upgrade processes very cumbersome and required coordinated releases, which goes against our CI/CD processes and does not separate our concerns out well enough. So while a viable solution, this was not the solution we were looking for.
This led me to look for a new answer, something that would support the sandboxed approach of iFrames, with the ability for apps to live natively on the page (as with Angular Elements). After digging for awhile, I was unable to find a solution to this problem.
So why do these apps only show up when the top one is available? To figure this out, we have to dig into how Angular compiles our files.
When we look under the hood, Webpack is being used to create our bundles. Knowing this and digging in, I discovered the Webpack creates a single namespaced object on the window. To see this, open your console on any page with an Angular App on it and type window.webpackJsonp. Once you see that, you will see all the objects that webpack has namespaced underneath this object.
This gave me the idea to try to rename the outputted namespace. Angular doesn’t give you the ability to do this natively, but luckily, after playing with Angular Elements, I was able to play with the tool Ngx Build Plus. This is a very powerful tool that allows you to configure Webpack for your Angular build process.
This tool works for both Nrwl Nx and a regular Angular application. In this tutorial, we will look at just implementing in an existing standalone Angular App.
To start, run “npm install ngx-build-plus -D”
You can than add build plus as your projects builder by running “ng add ngx-build-plus”
We have now set up Angular’s builder to use ngx-build-plus, which means we just need to configure our build process.
To do this, we just need to add a file at our root level; we will call this webpack.extra.js. Once you have that created, you just need to add the following:
Replace ‘namespacedName’ with your application’s unique namespaceName. For this example, I used webpackApplicationOne and webpackApplicationTwo.
Now we just need to run our build with one extra parameter: “ng build — prod — extra-webpack-config webpack.extra.js”.
When I combine my two apps into a single index.html and run it, I get:
We can see our app is now up and running! There are some disadvantages and limitations with this approach. The first one is what you can do with the router (There are two apps fighting for the same routes). If you only have one routeable app, this shouldn’t be an issue.
This also means that you are loading Angular and all shared dependencies twice. This can lead to a performance decrease, but with some tricks to load apps on scroll or after page load, and with Ivy’s new tree shaking, hopefully you can get each smaller application down to a manageable load time. Please feel free to comment and share your experiences with this.
Nick Favero is an Angular advocate who loves the environment it provides. He is a Senior Software Engineer at DHI Group/Dice.