Micro Frontends in Angular

What are Micro Frontends?

Micro frontends extend the concept of microservices to the frontend world, coined first by Thoughtworks. In traditional frontend development, a single application might handle everything from user interfaces to business logic. Micro frontends break this monolithic approach into smaller, more manageable pieces.

Here’s a high-level overview of micro frontends:

  1. Modularity: Just as microservices break down backend services into smaller, independent pieces, micro frontends decompose the frontend into smaller, self-contained applications or components. Each of these pieces can be developed, tested, and deployed independently.

  2. Ownership: Different teams can own different micro frontends. For instance, one team might handle the user profile section, while another team focuses on the shopping cart. This can help with scaling teams and allows for specialized focus.

  3. Technology Agnostic: Micro frontends can use different technologies or frameworks. One micro frontend could be built with React, while another might use Vue.js or Angular. This flexibility can be beneficial for integrating new technologies gradually.

  4. Integration: The individual micro frontends are integrated into a single application. This can be done through various methods, such as embedding iframes, using JavaScript-based integration techniques, or through build-time composition.

  5. Deployment: Micro frontends can be deployed independently. This means updates or changes in one micro frontend don’t necessarily affect others, which can reduce deployment risk and time.

  6. User Experience: Despite being developed and deployed separately, micro frontends aim to provide a seamless user experience. The integration points should be designed to ensure that the application feels cohesive to users.

Module Federation

  • Code split into smaller deployable modules

  • Can be shared and consumed at runtime between applications

  • Concepts: Host, Remote and Federate Module
    Host is an application that consumes federated modules from remote applications at runtime

    Remote is an application that exposes a federated module that can be fetched over the network at runtime

    Federated Module is any valid JavaScript module that is exposed by a remote application with the aim that it will be consumed by a host application

Native Federation

Native Federation is a browser-native implementation that can be used independently of build tools and frameworks

  • Avoids the complexity and overhead of configuring tools like Webpack.

  • Facilitates the direct use of native JavaScript functionalities for module imports.

  • May reduce build time overhead but heavily depends on browser support.

Step-wise setup

Create a Shell/Host app and N Micro frontend apps based on your need.
Our setup would be @angular-architects/native-federation library.

  1. Create angular apps

     ng new shell --routing --style=scss
     ng new mfe1 --routing --style=scss
     ng new mfe2 --routing --style=scss
    
  2. Add native federation library to each project.

     cd shell
     ng add @angular-architects/native-federation
     cd mfe1
     ng add @angular-architects/native-federation
     cd mfe2
     ng add @angular-architects/native-federation
    
  3. Configure settings for Native federation: Shell

    • The above commands generate federation.config.js file with content like below inside each project. Remove exposes property from shell as we do not expose anything from the Shell app.

        const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config');
      
        module.exports = withNativeFederation({
          name: 'shell',
          exposes: .....   // remove
          shared: {
            ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
          },
          skip: [
            'rxjs/ajax',
            'rxjs/fetch',
            'rxjs/testing',
            'rxjs/webSocket',
          ]
        });
      
    • Create the assets folder under /src

  4. For each MFE,

    • Configure federation.config.json to expose component/routes if needed.

    • Modify angular.json to change port where we serve the app.

    • Our shell runs on port 4200 by default. The MFEs need to run on separate ports.

        "architect": {
          "serve-original": {
            "options": {
              "port": <change-port-here>
            }
          },
        }
      
  5. Under Shell, create a new file federation.manifest.json file inside the assets folder with following content

     {
       "mfe1": "http://localhost:4201/remoteEntry.json",
       "mfe2": "http://localhost:4202/remoteEntry.json"
     }
    
    • Modify angular.json to add assets path under esbuild options.

        "esbuild": {
              "options": {
                "assets": [
                 {
                  "glob": "**/*",
                  "input": "public"
                 },
                 "src/assets"
                ],
              },
            }
      
    • Add module federation initialization inside main.ts . This is the critical part where we dynamically load MFEs before we bootstrap our Shell app.

        import { initFederation } from '@angular-architects/native-federation';
      
        initFederation('/assets/federation.manifest.json')
          .catch((err) => console.error(err))
          .then((_) => import('./bootstrap'))
          .catch((err) => console.error(err));
      
  6. Configure routes in Angular

     export const routes: Routes = [
       {
         path: 'mfe1',
         loadComponent: () => loadRemoteModule('mfe1', './Component').then((m) => m.AppComponent),
       },
       {
         path: 'mfe2',
         loadComponent: () => loadRemoteModule('mfe2', './Component').then((m) => m.AppComponent),
       },
     ];
    

    Run MFEs

     cd mfe1
     npm start
    
  7. Run shell

     cd shell
     npm start
    

Viola!

This is how our shell looks

This is how the MFE is hosted

Though the setup looks easy, the advanced configurations get tougher :).

MFE provides with many benefits

  • Faster deployment and Faster releases = reduced costs

  • Multiple teams with different responsibilities

  • Technology freedom

  • Easy scaling

  • Continuous deployment

MFE also has some challenges which cannot be overlooked

  • Design consistency

  • More repos, more pipelines

  • Micro frontend Anarchy - Details in next blog :)

That’s all folks!

0
Subscribe to my newsletter

Read articles from Shaunak Deshpande directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shaunak Deshpande
Shaunak Deshpande