In the rapidly evolving landscape of web development, the concept of micro frontends has emerged as a powerful approach to building scalable and maintainable web applications. This architecture allows teams to develop and deploy independent parts of a web application, which can be integrated into a larger application at runtime. A key technology enabling this architecture is Module Federation, introduced in Webpack 5, which allows JavaScript applications to dynamically load code from other applications at runtime.

This blog post delves into setting up a shell application and a remote micro frontend using Angular, Web Components, and Module Federation based on @angular-architects/module-federation.
The source code of a demo implementation is provided at GitLab.

Setting Up the Module Federation

Create a New Angular Application
Start by creating a new Angular application. Use the Angular CLI to generate a new project with or without a standalone component configuration.
ng new --no-standalone mfe-1


Add Module Federation Plugin
Use the Angular Architects’ Module Federation plugin to configure your application as a remote one.
ng add @angular-architects/module-federation --type remote --port 4201

Take a quick look at the updated and created files.

Option 1: Expose an Angular Module as a Micro Frontend

Create a new Angular Module ng g module mfe2-test --routing and include your Micro Frontend Components ng g component welcome.
In the webpack.config.js, expose the micro frontend’s entry point, and define shared dependencies.
This configuration allows the micro frontend to be dynamically loaded by the shell application.

const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');

module.exports = withModuleFederationPlugin({

  name: 'mfe-2',
  filename: "remoteEntry.js",
  exposes: {
    './Mfe2TestModule': './src/app/mfe2-test/mfe2-test.module.ts',
  },

  shared: {
    ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),

  },

});


Option 2: Expose an Angular Application as a Web Component

Building an entire Angular Application as a Web Component and publishing it using Module Federation involves several steps. This process allows the encapsulation of an Angular application into a single reusable Web Component, which can then be dynamically loaded into other applications using Module Federation.

Add Angular Elements
Angular Elements is a package that allows Angular components to be packaged as custom elements, which can be used in any web application.
ng add @angular/elements


Adjust AppModule for Web Component Creation
Modify the AppModule to use the createCustomElement function from @angular/elements to create a Web Component. This is done by removing the bootstrap: [AppComponent] line and using createCustomElement in the ngDoBootstrap method. The approach allows the application to be bootstrapped as a Web Component. For switching between bootstrapping as a Web Component and bootstrapping with Angular optional logic like below can be built.

import {ApplicationRef, DoBootstrap, Injector, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';

import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {environment} from '../environments/environment';
import {createCustomElement} from '@angular/elements';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
})
export class AppModule implements DoBootstrap {
  constructor(private injector: Injector) {
  }

  ngDoBootstrap(app: ApplicationRef) {
    if (environment.production) {
      // bootstrap as webcomponent for production
      const ce = createCustomElement(AppComponent, {injector: this.injector});
      customElements.define('mfe1-root', ce);
    } else {
      // bootstrap with angular. for local development
      app.bootstrap(AppComponent);
    }
  }

}

Change the index.html Tag
The tag in index.html is changed to the defined element tag in AppModule. This step is crucial to ensure the custom element is correctly recognized and rendered in the host application.

<!-- bootstrap with angular--> 
<app-root></app-root>
<!-- bootstrap webcomponent-->
<mfe1-root></mfe1-root>

Expose the Web Component using Module Federation
In the webpack.config.js, expose the micro frontend’s Web Component entry point, and define shared dependencies.
This configuration allows the micro frontend to be dynamically loaded by the shell application.

const {withModuleFederationPlugin} = require('@angular-architects/module-federation/webpack');

module.exports = withModuleFederationPlugin({

  name: 'mfe-1',
  filename: "remoteEntry.js",
  exposes: {
    './Mfe1AngularElement': './src/bootstrap.ts',
  },

  shared: {
    "@angular/core": {requiredVersion: "auto"},
    "@angular/common": {requiredVersion: "auto"},
    "@angular/router": {requiredVersion: "auto"},
    "rxjs": {requiredVersion: "auto"},
  }

});


Integrating the Micro Frontend into the Shell Application

Micro Frontend Demo Project

Add Module Federation Plugin
Use the Angular Architects’ Module Federation plugin to configure your application as a host and check the updated and created files.
This setup allows the shell application to load the micro frontend dynamically.
ng add @angular-architects/module-federation --type host --port 4200

Take a quick look at the updated and created files.

Load Micro Frontend using a Router
For loading the micro frontend dynamically into the shell application while accessing a specific route, use Angular’s router.

import { loadRemoteModule } from '@angular-architects/module-federation';
import {startsWith, WebComponentWrapper, WebComponentWrapperOptions} from '@angular-architects/module-federation-tools';
...
// Option 1: Load exposed Angular Module as a Micro Frontend
{
    path: 'mfe2',
    loadChildren: () =>
      loadRemoteModule({
        type: 'module',
        remoteEntry: 'http://localhost:4202/remoteEntry.js',
        exposedModule: './Mfe2TestModule'
      })
      .then(m => m.Mfe2TestModule)
},
// Option 2: Load exposed Angular Application as a Web Component
{
    // used because microfrontend has own router -> mfe needs to have same path
    matcher: startsWith('mfe1'),
    component: WebComponentWrapper,
    data: {
      type: 'module',
      remoteEntry: 'http://localhost:4201/remoteEntry.js',
      exposedModule: './Mfe1AngularElement',
      elementName: 'mfe1-root'
    } as WebComponentWrapperOptions
},

Load Several Angular Applications Together in One Browser Window

To enable multiple Angular applications to work together within a single browser window, leveraging Module Federation, requires a specific approach. The @angular-architects/module-federation-tools package provides a lightweight solution to facilitate this integration. 

First, install @angular-architects/module-federation-tools in both, your micro frontend and the shell application. This can be done using: npm i @angular-architects/module-federation-tools -D

This package is designed to simplify the process of setting up Module Federation in Angular applications, making it easier to manage dependencies and share code between different parts of your application.

Bootstrap with Module Federation Tools
Replace Angular’s default bootstrap method with the one provided by “@angular-architects/module-federation-tools“, for both applications, shell, and micro frontend! The bootstrap function from the package is used to initialize the application, with options specifying whether the application is a micro frontend or a shell.

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { bootstrap } from '@angular-architects/module-federation-tools';

bootstrap(AppModule, {
 production: environment.production,
 appType: 'microfrontend' // for micro frontend
 // appType: 'shell',      // for shell
});


Shared Dependencies and Module Federation Configuration

The shared section of the Module Federation configuration allows you to define libraries shared between your federated modules. This is crucial for preventing the same library from being loaded multiple times, optimizing the loading process, and ensuring that shared dependencies are correctly managed. For instance, if your shell application and micro frontends use Angular, you can define Angular libraries as singletons in the shared section to ensure a single download.

Further readings on Module Federation and dependency management with different versions:
https://www.angulararchitects.io/blog/multi-framework-and-version-micro-frontends-with-module-federation-your-4-steps-guide/

https://medium.com/@marvusm.mmi/webpack-module-federation-think-twice-before-sharing-a-dependency-18b3b0e352cb

https://www.angulararchitects.io/blog/the-microfrontend-revolution-part-2-module-federation-with-angular/