Angular: Integrating Micro-Frontends using ModuleFederation- Part-I

AngularEnthusiast
JavaScript in Plain English
7 min readMar 23, 2024

--

In this 4 part series, we will use the Module Federation technique offered by Webpack to achieve the integration of multiple micro-frontends within a container application.

=> In the first part, we will set up the shell/container application.

=>In the 2nd part, we will set up the 2 micro-frontend applications and integrate it with the shell application using the simple local development server.

=>In the 3rd part, we will create a shared library that will be used to fetch environment specific configuration data for each application.

=>In the 4th part, we will deploy the containerized applications to a Kubernetes cluster.

Below is a simple diagram of the structure.

  1. We have a container/shell application running on port 5004.
  2. We are integrating 2 remote micro-frontends running on port 5006 and 5008 respectively within the container/shell application.
  3. We have a common library to fetch the environment specific configuration data for each of the applications. Only a single instance of the library is shared between the 3 applications.

Below are 2 short demos of what we are trying to achieve in 2 environments. Its a very simple application.

Lets begin.

  1. I have created a shell application using the below command.
ng new shell-application

2. Next we add the @angular-architects/module-federation npm package to the angular project we just created using the below command. We are passing the port, we want to run the application on, using the port flag. Shell application runs on port 5004.

ng add @angular-architects/module-federation@"^16.0.4" --port 5004

Output: As you can see below, 3 new files :webpack.config.js and webpack.prod.config.js and bootstrap.ts have been created. Also multiple other files have been updated.

C:\Users\User\angular\microfrontends\module-federation-updated\shell-application>ng add @angular-architects/module-federation@”^16.0.4" — port 5004
i Using package manager: npm
√ Package information loaded.
The package @angular-architects/module-federation@^16.0.4 will be installed and executed.
Would you like to proceed? Yes
√ Packages successfully installed.
? Project name (press enter for default project) shell-application
CREATE webpack.config.js (1604 bytes)
CREATE webpack.prod.config.js (46 bytes)
CREATE src/bootstrap.ts (214 bytes)
UPDATE tsconfig.json (823 bytes)
UPDATE tsconfig.app.json (185 bytes)
UPDATE angular.json (2694 bytes)
UPDATE package.json (1238 bytes)
UPDATE src/main.ts (58 bytes)
√ Packages installed successfully.
C:\Users\User\angular\microfrontends\module-federation-updated\shell-application>

3. Lets now look at how the webpack.config.js looks like.

In this file, I have made only a single change to the shared property by adding the below object. module-federation-config-lib is the library we have created to fetch environment specific configurations of the 3 applications in the respective applications. Please note that although, we will be installing this library in all the 3 applications, only a single instance of the library will be shared between the 3 applications. This is the magic of Module Federation :)

“module-federation-config-lib”: {
singleton: true,
strictVersion: true,
requiredVersion: ‘auto’
},

3. The shell application just contains a navigation bar with 2 links :ToDos and Users. Clicking on these 2 links loads the micro-frontends in the <router-outlet>.

Below is the AppComponent Template. <app-header> is the selector for the HeaderComponent which has the navigation bar.

<div class=”container”>
<app-header></app-header>
<router-outlet></router-outlet>
</div>

Below is the HeaderComponent Template.

<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand">Microfrontends({{config?.env}})
<img src="assets/microfrontend.png"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>

<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" [routerLink]="['todos']">ToDos</a>
</li>

<li class="nav-item active">
<a class="nav-link" [routerLink]="['users']">Users</a>
</li>

</ul>
</div>
</nav>

The 2 main points in the template are:

=> The <a> tag where we are displaying an image and also the environment details. Why is the image so important ? We have used images in the shell and the 2 micro-frontend applications to demonstrate how static assets are fetched using proxy in web dev server and nginx web server.

<a class="navbar-brand">Microfrontends({{config?.env}}) 
<img src="assets/microfrontend.png"></a>

=> The links in the navigation bar enable us to load the 2 micro-frontends. Clicking on the ToDos link loads the first micro-frontend and clicking on the Users link loads the 2nd micro-frontend.

<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" [routerLink]="['todos']">ToDos</a>
</li>

<li class="nav-item active">
<a class="nav-link" [routerLink]="['users']">Users</a>
</li>

</ul>

4. Checking the routing definition which determines, how the micro-frontends are loaded when the links in the navigation bar are clicked. Please note that we are lazily loading the micro-frontends when clicking on the links in the navigation bar.

We are loading the remote micro-frontends via a manifest file(Please see the line type:’manifest’).

remoteName is the unique name we have assigned to the each micro-frontend. We have configured the same names in the micro-frontend applications as well. We will verify in the next story in this series.

exposedModule is the module ,the micro-frontend has exposed for the shell application to load. This may/may not be the exact module name. It could also be an alias name for the actual module the micro-frontend is exposing to the shell application. In this example, it is an alias name I have given for the module, the micro-frontend is exposing.

Within the then block highlighted below, we have provided the actual module name exposed by the micro-frontend. Please ensure it matches the exact module name, you have used when creating in the micro-frontend application.

We have created a manifest file : mf.manifest.json within the src/assets/manifest folder of the shell application.

Below are the contents of the mf.manifest.json. Right now everything is hardcoded in the below file. This is useful for local development. When deploying to the Kubernetes cluster in Part-4 of this series, we will see how can everything be made dynamic.

{
"toDoApp": "http://localhost:5006/remote-toDoApp.js",
"usersApp":"http://localhost:5008/remote-usersApp.js"
}

The key in the above must exactly match the remoteName in the routing definition. Below is the routing definition for reference. The remoteName property has been highlighted.

The value corresponding to the key is the remote micro-frontend entry points.

Observe the ports in the endpoints. As we mentioned in the beginning of the story, the 2 micro-frontends are running on ports 5006 and 5008 respectively. We will see later in the micro-frontend projects where the JS files remote-toDoApp.js and remote-usersApp.js are configured.

When is this mf.manifest.json loaded?

Below is the main.ts file where we are loading the mf.manifest.json

The helper function called loadManifest() allows to load the manifest. By default, loadManifest() also loads all the remote entry points. However, there is a second optional parameter called skipRemoteEntries you can set to true to avoid this.

We don’t want to load the files remote-toDoApp.js and remote-usersApp.js on shell application load. We want to load these files only when the micro-frontend actually needs to load i.e on clicking on the links in the navigation bar. Hence we have passed true as a second parameter to the loadManifest() as you can see below.

loadManifest(‘assets/manifest/mf.manifest.json’,true)

5. Finally, how do we load assets within the micro-frontends from the shell application ?

Right now I am providing the local development solution. When we are deploying the applications to a Kubernetes cluster, we shall update the nginx webserver configuration to handle this.

Lets create a proxy.conf.json in the root of the project with the below contents.

This means that any requests containing “/toDoApp/assets” will be proxied to the localhost:5006 and requests containing “/usersApp/assets” will be proxied to localhost:5008.

When proxying the request, we also rewrite the request containing “/toDoApp/assets” to “/assets/” and “/usersApp/assets” to “/assets/”.

The purpose of the rewrite being, the images are directly stored under the assets folder in both the micro-frontend applications and not within any folder inside the assets.

In short, we are asking the remote micro-frontend application to handle these requests.

6. We are now ready to start the shell application with the “npm run start”. The “start” script in the package.json.

“start”: “ng serve — proxy-config=proxy.conf.json”,

Hitting localhost:5004 in the browser, renders the shell application as below. We have a simple navigation bar with 2 links : ToDos and Users.

You can find the shell application project at the git repository below.

Please check the next story in this series to see how the micro-frontend applications have been setup and how we finally integrate them within the shell application.

In Plain English 🚀

Thank you for being a part of the In Plain English community! Before you go:

--

--

Loves Angular and Node. I wish to target issues that front end developers struggle with the most.