Angular: Integrating Micro-Frontends using ModuleFederation-Part II

AngularEnthusiast
JavaScript in Plain English
11 min readMar 23, 2024

--

In the previous story in the series, we set up the shell/container application. Below is the link.

In this story, we will set up the 2 micro-frontends and integrate them into the shell application.

Our first micro-frontend basically gives us the capability to load a list of dummy todos and we can also view the detail of each todo.

Our second micro-frontend gives us the capability to load a list of 10 dummy users and here again we can view the detail of each user.

I. Setting up first micro-frontend application.

  1. I have created a new application todoApp using the below command.
ng new toDoApp

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. The toDoApp application runs on port 5006.

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

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\toDoApp>ng add @angular-architects/module-federation@”^16.0.4" — port 5006
ℹ 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) toDoApp
CREATE webpack.config.js (1586 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 (2646 bytes)
UPDATE package.json (1230 bytes)
UPDATE src/main.ts (58 bytes)
✔ Packages installed successfully.
C:\Users\User\angular\microfrontends\module-federation-updated\toDoApp>

3. Lets now look at how the webpack.config.js looks like. I have made few changes to this file to expose the desired module to shell application and also to use the shared library.

To use the shared library, I have updated the shared property in the file with the below object. This is exactly what I did in the shell application as well.

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

To expose the module,I have updated the below properties.

  name: "toDoApp",
filename: "remote-toDoApp.js", //filename is of your choice
exposes: {'./myToDoModule': './src/app/to-do/to-do.module.ts'},

The name property can contain any name but it must be unique to this application alone.

filename property by default will be remoteEntry.js. I have modified it to remote-toDoApp.js to easily identify it.

The exposes property is an object which could contain a list of key-value pairs. The key can be any name or the module name itself. The value is the path to the module the micro-frontend wants to expose to the shell application.

Lets do a recap of the shell application routing definition. Below is the screenshot.

Below are the 2 observations:

=>The remoteName property in the first definition above matches the name property in the webpack.config.js in the toDoApp application.

=>The exposedModule property in the first definition above matches the key of the exposes property in the webpack.config.js in the toDoApp application.

Lets recap the mf.manifest.json file in shell application.

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

Making an observation here: The JS file in the first remote entry point above, matches the filename property in the webpack.config.js in the toDoApp application.

I hope this helps you make a connection between the shell and the toDoApp setup.

4. Let me give you a brief idea on how the toDoApp works. I wont go much into details. I will highlight the important points.

The toDoApp contains a ToDoModule which is exposed to the shell application. As you can see, the module has 3 components. Please also note that we have created an InjectionToken appName and registered it with the providers. The token bears a static value “toDoApp” which is nothing but the name of the micro-frontend application. This token can be injected anywhere in the application to access the name of the application.

Let me walk you through how each component looks like in the application.

=> Clicking on the Todos link in the navigation bar of the shell application loads the ToDoModule of the toDoApp application.

The ToDoContainerComponent of the ToDoModule is loaded as below when we click on the ToDos link in the navigation bar.

Observe the image in the ToDoContainerComponent above. Below is the <img> tag displaying the image.

    <img src="/{{appName}}/assets/todo.jfif">

The image is directly stored under the assets folder as you can see below :

What is the appName property in the <img> tag ?If you recall, we stored the name of the application as the value of an injection token in the ToDoModule.

export const appName=new InjectionToken(“appName”);

We have registered this in the providers section of the ToDoModule.

providers:[{
provide:appName,
useValue:”toDoApp”
}
]

We can now inject this injection token anywhere in the application to access the name of the application. This reduces the chances of typo errors.

In the ToDoContainerComponent, we have referenced the injection token “appName” as below in the constructor of the class.

@Inject(appName)public appName:string

You might ask the question, why we are appending an additional string “toDoApp” in the src attribute of the <img> ?

The purpose of adding the additional string is to enable the shell/container application to differentiate between the assets of the 2 micro-frontend applications.

Based on the additional string, the shell application will proxy the request for a particular asset to the correct micro-frontend application.

=> When we click on the “Load Todos” button in the ToDoContainerComponent, the ToDoListComponent shows up as below displaying a table with a list of dummy todos.

=>When we click on the “View Details” button in the last column of any todo in the above table, the ToDoDetailComponent shows up, displaying a table with more details on the respective todo.

Below is the link to the toDoApp git repository.

II. Setting up the 2nd micro-frontend application.

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

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. The toDoApp application runs on port 5008.

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

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\usersApp>ng add @angular-architects/module-federation@”^16.0.4" — port 5008
ℹ 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) usersApp
CREATE webpack.config.js (1588 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 (2650 bytes)
UPDATE package.json (1230 bytes)
UPDATE src/main.ts (58 bytes)
✔ Packages installed successfully.
C:\Users\User\angular\microfrontends\module-federation-updated\usersApp>

3.Lets now look at how the webpack.config.js looks like. I have made few changes to this file to expose the desired module to shell application and also to use the shared library.

To use the shared library, I have updated the shared property in the file with the below object. This is exactly what I did in the shell and the toDoApp application as well.

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

To expose the desired module to the shell application, I have updated the below properties.

    name: "usersApp",
filename: "remote-usersApp.js",
exposes: {
'./myUsersModule': './src/app/users/users.module.ts',
},

The name property can contain any name but it must be unique to this application alone.

filename property by default will be remoteEntry.js. I have modified it to remote-usersApp.js to easily identify it.

The exposes property is an object which could contain a list of key-value pairs. The key can be any name or the module name itself. The value is the path to the module the micro-frontend wants to expose to the shell application.

Lets do a recap of the shell application routing definition. Below is the screenshot.

Below are the 2 observations:

=>The remoteName property in the 2nd definition above matches the name property in the webpack.config.js in the usersApp application.

=>The exposedModule property in the 2nd definition above matches the key of the exposes property in the webpack.config.js in the usersApp application.

Lets recap the mf.manifest.json file in shell application.

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

Making an observation here: The JS file in the 2nd remote entry point above, matches the filename property in the webpack.config.js in the usersApp application.

4. Let me give you a brief idea on how the usersApp works.

The usersApp contains a UsersModule which is exposed to the shell application. As you can see, the module has 3 components.Please also note that we have created an InjectionToken appName and registered it with the providers. The token bears a static value “usersApp” which is nothing but the name of the micro-frontend application. This token can be injected anywhere in the application to access the name of the application.

Let me walk you through how each component looks like in the application.

=> Clicking on the Users link in the navigation bar of the shell application loads the UsersModule of the usersApp application.

The UsersContainerComponent of the UsersModule is loaded as below when we click on the Users link in the navigation bar.

Observe the image in the UsersContainerComponent in the screenshot above. The image is displayed using the <img> as below:

<img src="/{{appName}}/assets/user-logo.png">

The image is directly stored under the assets folder as you see below.

What is the appName property in the <img> tag ? If you recall,we stored the name of the application as the value of an injection token in the UsersModule.

export const appName=new InjectionToken(“appName”);

We have registered this in the providers section of the UsersModule.

 providers:[
{
provide:appName,
useValue:"usersApp"
}
]

We can now inject this injection token anywhere in the application to access the name of the application. This reduces the chances of typo errors.

In the UsersContainerComponent, we have referenced the injection token “appName” as below in the constructor of the class.

@Inject(appName)public appName:string

You might ask the question here again, why we are appending an additional string “usersApp” in the src attribute of the <img> ?

The answer is the same: To enable the shell/container application to differentiate between the assets of the 2 micro-frontend applications.

Based on the additional string, the shell application will proxy the request for a particular asset to the correct micro-frontend application.

=> When we click on the “Load Users” button in the UsersContainerComponent, the UsersListComponent shows up as below. It displays a table with a list of 10 dummy users.

=> When we click on the “View Details” button in the last column of the above table for any user, the UserDetailComponent shows up as below, displaying the details of the user in a table.

Below is the link to the usersApp git repository.

This completes the setup of the 2 micro-frontend applications.

III. Demo

We can now start both the applications using “ng serve” or “npm run start”. The toDoApp will run on port 5006 and the usersApp will run on port 5008.

We already have the shell application running on port 5004 from the previous story in the series.

=>I hit localhost:5004 in the browser. Observe the network tab. There are no requests to call the remote entry points.

=> Clicking on the ToDos link in the navigation bar. Observe the request made to fetch the remote-toDoApp.js from localhost:5006. Please ignore the API calls in the network tab made to fetch the config.json files for now. We shall see this in the 3rd part of this series which is entirely about fetching environment specific configurations.

=> Clicking on the “Load Todos” button. API call made to fetch the list of dummy todos.

=> Clicking on the “View Details” button. API call made to fetch detail of a single todo.

=> Clicking on the Users link in the navigation bar. Request is made to fetch the remote-usersApp.js from localhost:5008.

=> Clicking on the “Load Users” button, an API call is made to fetch the list of 10 dummy users.

=> Clicking on the “View Details” button corresponding to a user makes an API call to fetch the detail of that particular user.

Lastly I want you to observe the network calls made to fetch the static assets i.e the images in the 3 applications.

=>Image displayed in the header of the shell application.

=>Image displayed in the toDoApp application. Note that in the absence of proxy.conf.json, the below request would have failed with 404. We have proxied http://localhost:5004/toDoApp/assets/todo.jfif to
http://localhost:5006/assets/todo.jfif.

=> Image displayed in the usersApp application. Here again, in the absence of proxy.conf.json, the below request would have failed with 404. We have proxied http://localhost:5004/usersApp/assets/user-logo.png to http://localhost:5008/assets/user-logo.png

This completes the integration of the 2 micro-frontend applications within a shell/container application using module federation. We have run the applications using local development server and used proxy.conf.json to redirect requests to the micro-frontend applications for fetching static assets.

Please check the next story in the series, where we will create a share library to set and get environment specific configuration details.

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.