TLDR; Selecting a way to implement micro frontends in an SPA comes down to knowing your architectural goals, ranking them, and weighing them against the available options. This article walks through that evaluation for a set of common goals and summarizes the outcome in a matrix. For additional orientation, this decision tree may also help:

I am regularly asked how SPAs and/or Angular fit into an environment built on microservices. The demand for microfrontends is unsurprising given how widespread microservices have become. The core idea behind microservices is straightforward: build a set of small applications -- microservices -- rather than a single monolithic one. Among other effects, this produces smaller teams (one per microservice) that can decide faster and pick the technology that best fits their needs.

When several microservices are meant to combine into one larger software system running in the browser, we need a mechanism to load them alongside each other and to keep them isolated, so they cannot interact in ways nobody planned. The added twist is that every team may work with different frameworks in different versions.

Luckily, multiple approaches exist. Unluckily, none of them is perfect -- each comes with its own trade-offs.

To settle on one, a software architect would assess these so called architectural candidates against the architectural goals defined for the system at hand. Typical (though not exhaustive) goals for SPAs in microservice-based settings appear in the next section.

What Are We Optimizing For?

Architectural Goal Description
a) Isolation Can the clients influence each other in an unplanned way?
b) Separate Deployment Can the microservices be deployed separately without the need to coordinate with other teams responsible for other microservices?
c) Single Page Shell Is the shell, composing the loaded microfrontends a SPA -- or does it at least feel like one for the user (no postbacks, deep linking, holding state)
d) Different SPA-Frameworks Can we use different SPA frameworks (or libraries) in different versions
e) Tree Shaking Can we make use of tree shaking?
f) Vendor Bundles Can we reuse already loaded vendor bundles or do we need to load the same framework several times, if it's used by several microfrontends
g) Several microfrontends at the same time Can we display several microfrontends at the same time, e. g. a product list and a shopping basket
h) Prevents version conflicts Does the approach prevent version conflicts between used libraries?
i) Separate development Can separate teams develop their microfrontends independently of other ones
j) One optimized solution (bundle) Everything is compiled into one optimized solution. You don't have to duplicate libraries or frameworks for different parts of the system.

In practice, you would decompose these abstract goals into concrete, measurable goals for your project. Since that is beyond the scope of an overview like this, I keep them at the abstract level.

Weighing the Options

The table below evaluates a number of architectural candidates for microfrontends against the goals discussed above (a - g).

Architectural Candidate a b c d e f g h i j
I) Just using Hyperlinks x x x x x x
II) Using iframes x x x x x x x x
III) Loading different SPAs into the same page S x x x x x x
IV) Plugins * S x x x x
V) Deployment Monolith using Packages (npm, etc.) * S x x x x x
VI) Deployment Monolith using Monorepos * ** S x x x x x x
VII) Web Components S x x x x x x

An x indicates that the goal in question is supported. The S in the Isolation column means you can rely on Shadow DOM together with EcmaScript Modules to achieve a degree of isolation.

* This does not produce a typical microservice-based solution. Still, since the main objective is reaching the defined goals, ignoring these options would be a mistake.

** Monorepos can be used with separate applications. In that case you would apply one of the other integration strategies described here.

If any of these candidates spark your interest, the next table offers some further thoughts on them:

Nr Remarks
I) Just using Hyperlinks We could save the state before navigating to another microfrontend. Using something like Redux (@ngrx/store) could come in handy b/c it manages the state centrally. Also consider that the user can open several microservices in several browser windows/ tabs.
II) Using iframes We need something like a meta router that synchronizes the url with the iframes ones. We also need to solve some issues like resizing the iframes to prevent scrolling within them and elements cannot overlap their borders. Also, iframes are not the most popular feature 😉
III) Loading different SPAs into the same page A popular framework that loads several SPAs into the browser is Single SPA. The main drawback seems to be the lack of isolation, cause all applications share the same global namespace and the same global browser objects. If the latter ones are monkey patched by a framework (like zone.js) this affects all the loaded SPAs
IV) Plugins Dynamically loading parts of a SPA can be done with Angular but webpack and so the CLI demands on compiling everything together. Switching to SystemJS would allow to load parts that have been compiled separately
V) Packages This means, providing each frontend as a package via (a private) npm registry or the monorepo approach and consuming it in a shell application. This also means, that there is one compilation step that goes through each frontend.
VI) Deployment Monolith To structure your deployment monolith into parts which can be developed and tested seperately, you can e. g. use npm packages or a monorepo.
VII) Web Components This is similar to III) but Web Components seem to be a good fit here, b/c they can be used with any framework - at least, in theory. They also provide a bit of isolation when it comes to rendering and CSS due to the usage of Shadow DOM. Angular Elements come very in handy for this. The idea is to compile Angular Components down to Web Components.

One thing should be clear: no perfect solution exists, and the right pick genuinely depends on your current situation and on how much weight you assign to each architectural goal. I have, for example, seen plenty of teams ship successful applications built on libraries, and companies such as Google and Facebook have a long tradition of working with monorepos. I also expect Web Components to see growing adoption, thanks to improving framework and browser support.

A Few Pointers

One way to work with the matrix presented here is to add every other goal you have and evaluate it against your own situation. Although that sounds easy, it can prove rather difficult in practice. That is why I decided to offer some (biased) guidance by highlighting the strengths of each approach:

If ... Try ...
1) You need just use one microfrontend at one time and have little/ no communication on UI level between them Hyperlinks
2) Otherwise: Legacy and need for very strong isolation iframes
3) Otherwise: One optimized and fully integrated UI, just one technology and no separate deployment Deployment monolith using mono repos
4) Otherwise Bootstrap several SPAs in one browser window, use Shadow DOM for Isolation, consider Web Components

If you have no legacy system and have already committed to microservices, you should consider 1) and 4), in exactly that order.

Macro Architecture Versus Micro Architecture

When you pick a solution, remember that you need answers on two distinct architecture levels, which I refer to as the macro and the micro architecture:

Whereas the macro architecture acts as the glue between your micro apps, the micro architecture is the architecture inside them. This split has proven handy for me, since you are free to pick different solutions on each level. The illustration uses web components on both levels, but you could equally choose iframes for the macro architecture and Angular packages for the micro architecture. Separating the levels keeps you from conflating several concerns while discussing your target architecture.

It Is Not an Either/Or Decision

Combining these approaches can be a good move as well. You might, for instance, rely on Hyperlinks for general routing and then fall back to libraries or web components whenever a widget from one microfrontend needs to be displayed inside another.