A lightweight and solid approach towards micro frontends (micro service clients) with Angular and/or other frameworks

Even though the word iframe causes bad feelings for most web devs, it turns out that using them for building SPAs for micro services -- 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. Another great resource comparing the options available is Brecht Billiet's presentation about this topic.

In addition to this, I've written another blog post comparing several approaches by evaluating them against some selected architectural goals.

As Asim Hussain shows in this blog article, using iframes can also be a nice solution for migrating an existing AngularJS application to Angular.

For the approach described here, I've written a "meta router" to load different spa clients for micro services in iframes. It takes care about the iframe's creation and about synchronizing their routes with the shell's url. It also resizes the iframe dynamically to prevent a scrolling bar within it. The library is written in a framework agnostic way.

The router can be installed via npm:

npm install meta-spa-router --save

The source code and an example can be found in my GitHub account.

In the example I'm using VanillaJS for the shell application and Angular for the routed child apps.

This is how to set up the shell with VanillaJS:

var MetaRouter = require('meta-spa-router').MetaRouter;

var config = [
    {
        path: 'a',
        app: '/app-a/dist'
    },
    {
        path: 'b',
        app: '/app-b/dist'
    }
];

window.addEventListener('load', function() { 

	var router = new MetaRouter();
	router.config(config);
	router.init();
	router.preload();


    document.getElementById('link-a')
            .addEventListener('click', function() { router.go('a') });

    document.getElementById('link-b')
            .addEventListener('click', function() { router.go('b') });

    document.getElementById('link-aa')
            .addEventListener('click', function() { router.go('a', 'a') });

            document.getElementById('link-ab')
            .addEventListener('click', function() { router.go('a', 'b') });        

}); 

And here is the HTML for the shell:

<div>
    <a id="link-a">Route to A</a> |
    <a id="link-b">Route to B</a> |
    <a id="link-aa">Jump to A within A</a> |
    <a id="link-ab">Jump to B within A</a>
</div>

<!-- placeholder for routed apps -->
<div id="outlet"></div>

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 RoutedApp class to establish a connection with the shell. This is necessary to sync the client app's router with the shell's one. As I'm using Angular in my example, I'm registering it as a service. Instead of this, one could also directly instantiate it when going with other frameworks.

To register this service that comes without Angular Metadata for AOT because its framework agnostic, I'm creating a token in a new file app.tokens.ts:

import { RoutedApp } from 'meta-spa-router';
import { InjectionToken } from '@angular/core';

export const ROUTED_APP = new InjectionToken<RoutedApp>('ROUTED_APP');

Then I'm using it to create a service provider for the RoutedApp class:

import { RoutedApp } from 'meta-spa-router';
[...]

@NgModule({
  [...],  
  providers: [{ provide: ROUTED_APP, useFactory: () => new RoutedApp() }],
  bootstrap: [AppComponent]
})
export class AppModule { }

In the AppComponent I'm getting hold of a RoutedApp instance by using dependency injection:

// app.component.ts in routed app

import { Router, NavigationEnd } from '@angular/router';
import { Component } from '@angular/core';
import { filter } from 'rxjs/operators';
import { RoutedApp } from 'meta-spa-router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';

  constructor(
    private router: Router, 
    @Inject(ROUTED_APP) private routedApp: RoutedApp) {
    this.initRoutedApp();
  }
  
  initRoutedApp() {
    
    this.routedApp.config({ appId: 'a' });
    this.routedApp.init();

    this.router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe((e: NavigationEnd) => {
      this.routedApp.sendRoute(e.url);
    });

    this.routedApp.registerForRouteChange(url => this.router.navigateByUrl(url));
  }

}

I'm assigning an appId which is by convention the same as the child app's path in the shell. In addition to that, I'm also synchronizing the meta router with the child's apps one.

 

 
 
 

Schulungen

Angular Schulung: Strukturierte Einführung

Lernen Sie in dieser interaktiven Schulung anhand einer Beispielanwendung den Einsatz von Angular für Ihre erfolgreichen Projekte kennen. Sie durchdringen die Hintergründe und bauen von der ersten Minute an auf Best Practices auf.

Details

Advanced Angular Schulung: Enterprise-Anwendungen und Architektur

In dieser Schulung erfahren Sie alles für die Entwicklung großer Anwendungen mit Angular: Mono-Repos, Micro-Apps, State Management, Performance und mehr

Details

Migration von AngularJS 1.x auf Angular (2+)

Seit der Ankündigung von Angular (2+) fragen sich Entwicklungs-Teams, welche Migrationspfade für AngularJS-1.x-Anwendungen vorliegen werden. Das im Lieferumfang von Angular enthaltene ngUpgrade bietet eine Antwort darauf. Es erlaubt einen Parallelbetrieb von AngularJS 1.x und Angular (2+) und stellt somit die Grundlage für eine schrittweise Migration dar.

Details

Progressive Web-Apps mit Angular

Progressive Web Apps bieten den Komfort nativer Anwendungen, indem sie auf moderne Browser APIs, wie Service Worker, setzen. Sie sind installierbar sowie offlinefähig und nutzen Hintergrundprozesse für Datensynchronisation und Push-Notifications. Diese Schulung zeigt anhand eines durchgehenden Beispiels was sich genau hinter diesem neuen Konzept verbirgt, wie solche Anwendungen mit Angular entwickelt werden und wie Sie in Ihren Projekten von den dahinterstehenden Ideen profitieren.

Details

Reaktive Architekturen mit Angular und Redux

Dieses interaktive Seminar vermittelt, wie Sie reaktive Anwendungen mit Angular entwickeln können.

Details

TypeScript

TypeScript gibt Ihnen alle Möglichkeiten der neuen JavaScript-Standards und zusätzlich ein statisches Typsystem, das dabei hilft, Fehler möglichst früh zu erkennen. Außerdem ist TypeScript die Grundlage für Angular. In diesem interaktiven Seminar lernen Sie diese mächtige Sprache anhand einer Fallstudie kennen.

Details

Weitere Schulungen ...