Building A Micro-Frontend Using React And Angular

Building A Micro-Frontend Using React And Angular

An introduction to building micro-frontends along with the best scenarios to implement them

What are micro-frontends?

Micro-frontends are a testing method which provide multiple features or modules for web apps owned by independent teams to making them more user-friendly and less bulky. They basically divide a frontend app into individual and semi-independent microapps so that each application can be of a different technology, be it React, Angular or Vue, which can then be easily integrated into a single app.

Why do we need micro-frontends?

Consider you are working on a project on a particular framework or a library (like React.js) but you are required to switch to another other framework or library or add another module which has been written on a differant framework (like Angular.js). Without a micro-frontend you will have to re-write the whole project or module which is a tedious process.

Another scenario is if you are working on a big project with multiple teams which makes collaboration a task. When a codebase is large, components and pages are required to be connected because of which you sometimes end up overlapping your work with that done by other team members. This causes further re-writes, more complexity and mismanagement of time as well cause a delay to the entire development process. What if you don't have to change anything and you can just start adding the new modules with another framework of your choice? This is where micro-frontends come in.

They help us to write apps in multiple frameworks (even Vanilla JS) and load them together using the same router and domain. We can develop the main parent app which will contain the authentication and routing implemented and we can then proceed to adding multiple child apps which work independently and can be loaded on either the same or different pages.

How to build micro-frontends?

Now let's see how to build a real app and how to integrate two frameworks, React and Angular, using micro-frontends. The first question that comes into play here is how we should divide apps as there is no specific criteria to divide them. We can do this in quite a few ways as per our requirement. Let's look at some of them below:

  • Feature Wise

This is most commonly used division where we will divide the features or modules of the application. For example, let's say there are three features on the dashboard, we can also have three micro-frontends for each respective feature with the dashboard as the common stage.

  • Page Wise

In some of the apps, functionalities are divided by page. We can divide the apps by page and each page will have independent functionalities when using this method.

  • Domain Wise

Apps can be divided as per domain as well. For example, we can divide the app in either the Core Domain, Payments Domain or the Profile Domain based on our requirement.

There are two ways of implementing sub-applications on a web page:

  • One application on each page

  • All the sub-applications on a single page

The requirements:

As each micro-frontend will be placed at specific location and will have its own API's, we need to have a base which will render the application at specific location. Here are few ways by which we can achieve this:

  • Webpack module federation

  • NGINX

  • iFrames

  • Web components

  • H-include library

  • Single SPA library

Here we will be focusing on Single SPA library as it has features like:

  • Lazy loading of code improving initial load time

  • Use of multiple frameworks on single page

Project Structure

We will build three modules, i.e., main-app in React, sub-app in React and a sub-app in Angular. We can use create-react-app for creating main-app, sub-app for React and Angular CLI for creating sub-app in Angular.

The Implementation

We will have to use certain functions to register our sub-applications in the main-app for exporting our sub-applications. Fortunately, we don't have to implement these functions manually as a single SPA can handle these by itself for Angular and React.

  1. Implementations in the Sub-application

To export a module as a sub-app, we will have to export the following lifecycle functions:

  • bootstrap - This will be called once, right before the registered app is mounted for the first time.

  • mount - This will be called whenever the registered app is mounted.

  • unmount - This will be called whenever the registered app is unmounted.

  • We also have to provide the rootComponent and domElementGetter where the rootComponent is used to render the React app in which case the latter returns the DOM element to which the app was rendered.
  • Here's the code snippet for implementing a React app entry file:
import React from 'react';
import ReactDOM from 'react-dom';
import singleSpaReact from 'single-spa-react';
import rootComponent from './App';

const reactLifecycles = singleSpaReact({
    React,
    ReactDOM,
    rootComponent,
    domElementGetter: () => document.getElementById('react-app')
});

export const bootstrap = [
    reactLifecycles.bootstrap,
];

export const mount = [
    reactLifecycles.mount,
];

export const unmount = [
    reactLifecycles.unmount,
];
  • We have to provide a *mainModule* (root Angular module), *domElementGetter* and *template* to single-spa-angular to the Angular file. Here's the code snippet for implementing an Angular app entry file:
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app/app.module';
import singleSpaAngular2 from 'single-spa-angular2';


const ng2Lifecycles = singleSpaAngular2({
  domElementGetter: () => document.getElementById('angular-app'),
  mainModule: AppModule,
  angularPlatform: platformBrowserDynamic(),
  template: `<app-root />`,
});

export const bootstrap = [
  ng2Lifecycles.bootstrap,
];

export const mount = [
  ng2Lifecycles.mount,
];

export const unmount = [
  ng2Lifecycles.unmount,
];

Now we have the sub-applications ready but you must be thinking about how the main application would find the bootstrap, mount and unmount functions. Once both the sub-apps are executed, the functions should automatically be accessible in a window.angularApp object along with the window.reactApp objects. Since we are using a single SPA function in both the sub applications, both the sub applications and the template will know the location of the Single SPA lifecycle functions using a global namespace .

Now the question is how do we set these locations of the sub apps?

To set the location of the sub apps, just add two entries to the module.exports.output object in the Webpack configuration file for each sub-application. You can see the example for an Angular app in the below code snippet (You can do the same for React app as well). Follow the example given below:

  module.exports = {
    output: {
      library: "angularApp",
      libraryTarget: "window"
    }
  };

Implementing the Main App

  • Add a single SPA library to the package.json file.

  • Register all the sub apps in a single SPA library.

  • Code snippets for starting the SPA file, registerReactApp and registerAngularApp are given below. Enter the following code snippet onto your console:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as singleSpa from 'single-spa';
import {registerReactApp} from "./apps/react-app";
import {registerAngularApp} from "./apps/angular-app";

ReactDOM.render(<App/>, document.getElementById('root'));


registerReactApp();
registerAngularApp();

singleSpa.start();
  • Use the following code snippet to register the registerReactApp:
import * as singleSpa from "single-spa";
import {matchingPathname, runScript} from "./utils";

const loadReactApp = async () => {
    await runScript('http://localhost:3002/static/js/main.js');
    return window.reactApp;
};


export const registerReactApp = () => {
    singleSpa.registerApplication('react-app', loadReactApp, matchingPathname(['/react', '/']));
};
  • Use the following code snippet to register the registerAngularApp:
import * as singleSpa from "single-spa";
import {matchingPathname, runScript} from "./utils";

const loadAngularApp = async () => {
    await runScript('http://localhost:3001/inline.bundle.js');
    await runScript('http://localhost:3001/polyfills.bundle.js');
    await runScript('http://localhost:3001/styles.bundle.js');
    await runScript('http://localhost:3001/vendor.bundle.js');
    await runScript('http://localhost:3001/main.bundle.js');
    return window.angularApp;
};


export const registerAngularApp = () => {
    singleSpa.registerApplication(
        'angular-app', 
        loadAngularApp, 
        matchingPathname(['/angular', '/'])
    );
};
  • As you must have noticed, both the sub applications and the will ask to know the DOM element ID of the sub application container. This is when you will have to think about how to implement a communication system between the apps.

Communication

The sub applications here are completely independent from each other but we can have them communicate with each other on certain events by using a library like an Eev event bus. AnEEv event bus is a small and fast zero-dependency event emitter that can help us to exchange information between the React and Angular apps. To know more about this transmitter, click here. You can use a native web browser event mechanism for the communication as well as it does not require any additional libraries.

Summary

  • Micro frontends streamline development in many cases and they are basically the implementation of micro services for the front end.

  • By using micro-frontends, we can make it easy to understand, develop, test and deploy in large scale applications even with complex web apps.

  • Each sub app can be developed independently on a different stack and can be owned by a single team or multiple teams when using a micro-frontend.

  • There are many advantages of using this approach but remember this should make your life easy. They are not meant to be used for small applications.

Conclusion

Micro-frontends are really powerful and a lot of big organisations are using this on a large-scale level these days to make the development process more streamlined. It is an open source resource which is under constant development and is being explored and tested to improve it. You can combine smaller apps and create big frontend applications using micro-frontends but it is not prudent to use it for all kind of apps. Understanding your application can give you a clearer picture about which scenarios to implement micro-frontends in order to use their benefits in the best possible way.

Hope this article has helped you understand the topic of micro-frontends better.