Even though the word iframe causes bad feelings for most web devs, it turns out that using them for loading SPAs in a micro service based environment -- aka micro frontends -- is a good choice. For instance, they allow for a perfect isolation between clients and for a separate deployment. Because of the isolation they also allow using different SPA frameworks. Besides iframes, there are other approaches to use SPAs in micro service architectures -- of course, each of them has their own pros and cons. A good overview can be found here.
As Asim Hussain shows in this blog article, using iframes can also be a nice solution for migrating an existing AngularJS application to Angular.
The approach described here uses a "meta router" to load different spa clients for micro services in iframes. It takes care about the iframes creation and about synchronizing their routes with the shells url. It also resizes the iframe dynamically to prevent a scrolling bar within it. Ive written this router in VanillaJS for two reasons: 1) It allows very slim shell applications that dont base upon any framework and 2) it allows using different frameworks for different micro frontends.
The router and an example that shows how to use it can be found in my GitHub repo.
This is what a shell that uses the meta router looks like:
<!-- index.html in shell -->
<a href="javascript:router.go(a)">Route to A</a> |
<a href="javascript:router.go(b)">Route to B</a> |
<a href="javascript:router.go(a, a)">Jump to A within A</a>
<a href="javascript:router.go(a, b)">Jump to B within A</a>
<div id="outlet"></div>
<script src="polyfills.js"></script>
<script src="router.js"></script>
<script>
var config = [
{
path: a,
app: /app-a/dist
},
{
path: b,
app: /app-b/dist
}
];
router.config(config);
router.init();
router.preload();
</script>
The router creates the iframes as children of the element with the id outlet
and allows switching between them using the method go
. As you see in the example, it also allows to jump to a subroute within an application.
The routed applications use the child-app.js
script and define an appId
which is the same as the used path
above:
<!-- index.html in routed app -->
<script src="../../child-app.js"></script>
<script>
childApp.config({ appId: a });
childApp.init();
</script>
To synchronize the shells url with the routed applications urls, we need to use Angulars Router
service:
// app.component.ts in routed app
import { Router, NavigationEnd } from @angular/router;
import { Component } from @angular/core;
import { filter } from rxjs/operators;
declare let childApp: any;
@Component({
selector: app-root,
templateUrl: ./app.component.html,
styleUrls: [./app.component.css]
})
export class AppComponent {
title = app;
constructor(private router: Router) {
this.initChildRouter();
}
// Sync Subroutes
initChildRouter() {
// send route to shell
this.router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe((e: NavigationEnd) => {
childApp.sendRoute(e.url);
});
// get route from shell
childApp.registerForRouteChange(url => this.router.navigateByUrl(url));
}
}
The source code can be found in my GitHub repo.