Module Federation using Angular Standalone Components — In 6 Steps
This is part 3 of the series on Module Federation.
- Module Federation Concepts: Clearly Explained with an Example
- Module Federation using Angular Standalone Components — In 6 Steps
- Dynamic Module Federation in Angular
This article thoroughly explores the module federation in Angular using Standalone Components from the ground up to understand how it works with an Angular application. We will start with a newly generated angular application and add module federation step-by-step.
This application examples and code are generated using Angular version 17.
You can find the source code used in this app here.
Step 0 — Our App Preview
We have a simple notes app with only two tabs: Notes and Customer. The app containing both notes and customers is the shell or the host app. However, the customer is an app(remote) in itself. The host uses the module federation to load the customer app and use it in its app.
Host: http://locahost:4200 Remote: http://locahost:4201


You can use your apps if you want to do hands-on or use the source code to understand the flow with steps.
Step 1— Custom Webpack Config
The module federation is being implemented using a plugin called ModuleFederationPlugin by Webpack and is not available by default in an Angular application. Therefore, the first step is to make our application accept a custom Webpack config where we can add the module federation plugin.
To do so we have to use the custom-webpack builder. This will allow us to add the custom Webpack config using a file.
npm i -D @angular-builders/custom-webpackNext, we need to make the following changes. - Replace build’s builder with custom-webpack:browser - Replace serve’s builder with custom-webpack:dev-server - Add path to the custom Webpack file in the options under customWebpackConfig - Change the “browser” key under options to “main”
...
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"main": "./src/main.ts",
"customWebpackConfig": {
"path": "./custom-webpack.config.js"
},
}
...
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
...
}
}This step is needed for both the remote and the host.
However, an extra step is needed for the remote to solve an issue that comes when both the host and the remote are running together using “ng serve” or the dev server.
...
"architect": {
...
"serve": {
...
"options": {
"publicHost": "http://localhost:4201"
},
}
}If we don’t set the above option, the host will keep refreshing frequently, getting constant updates from the remote.
Step 2— Adding Webpack Config for Remote
In this step, we will add the module federation plugin in the custom Webpack file for the remote. Some extra config options are in the same file, but I have added comments to explain those here to keep this article short.
Remember, the remote will expose something that other apps can use. name: The unique name of this module federation app. filename: The generated remote entry file for the remote. This will be used by the host to excess exposed modules. exposes: The modules we want to expose from this app. The key is the name given to the exposed module and the value is the module’s path. shared: The libraries we are sharing with other apps.
I have kept the shared config empty to explain some concepts. We will fill this in a bit later. We are sharing our customer standalone component.
// custom-webpack config
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
// Rest of the config
plugins: [
new ModuleFederationPlugin({
name: "angular-mfe-remote", // A unique name for this remote app
filename: "remoteEntry.js", //
exposes: {
"./Test": "./src/app/customer/customer.component.ts",
},
library: {
type: "module",
},
shared: {}
}),
],
}After this step, if you run the “npm run build” command, there will be a remoteEntry.js file generated as part of the build.
That is all for the remote! module federation will take care of everything behind the scenes and expose this module to other apps.
Step 3— Adding Webpack Config for Host
Now we will repeat the same step for the host to consume the modules exposed by the remote.
The host has the same config except instead of exposes, it has remotes. remotes: The modules that the host wants to consume. The key is the local name that the host is giving to the remote and value is the URL of the remote entry file of the remote.
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
// Rest of the config
plugins: [
new ModuleFederationPlugin({
name: "angular-mfe-host",
filename: "remoteEntry.js",
remotes: {
remote: "http://localhost:4201/remoteEntry.js",
},
shared: {
},
library: {
type: "module",
},
}),
],
};
Step 4— Implementing Bootstrap Pattern
We know in an Angular project, we have a main.ts file that is the entry point to our application and it contains the code to bootstrap our application.
We have to change it a bit for module federation as shared libraries are not available if we load and run our main application immediately. Hence, we will move our main.ts code to another file called bootstrap.ts. And main.ts will import this bootstrap.ts file using the dynamic import. This will give time for the application to load firs the remoteEntry.js file and decide on shared libraries.
// main.ts
import('./bootstrap').catch((err) => console.error(err));If you will do this, you will see the following error.
Error: Shared module is not available for eager consumptionAnother solution is to load libraries eagerly in the shell and make them part of the main build. But that is not common and we can see that later in another article.
Step 5— Accessing Remote from Host
We are all set to access the remote application in our host app. We can use the Javascript dynamic import to access our application similar to other modules we load in an Angular application.
export const routes: Routes = [
// This is local module
{
path: 'home',
loadComponent: () =>
import('./notes/notes.component').then((m) => m.NotesComponent),
},
// This is module from the remote app
{
path: 'customer',
loadComponent: () =>
import('remote/Customer').then((m) => m.CustomerComponent),
},
];The pattern for loading the remote module is
import(`${LOCAL_NAME_BY_HOST}/${REMOTE_EXPOSED_MODULE}`)You can see how seamless and easy it is to access the remote module using the module federation. But wait, you might be seeing an error while importing.
Cannot find module 'remote/Customer' or its corresponding type declarations.Typescript is not recognizing this module path and will give us the above error. To solve this issue, we usually declare a file at the root level called remotes.d.ts where we declare remote types. In our case, the file content would be
declare module 'remote/Customer';And this is all for the setup to handle communication between the apps. Start both apps using “npm start” and open the browser on localhost:4200 URL or your application URL.
Step 6— Solving Multiple Angular Version Issues
And you will see some console errors :( Don’t worry that's on purpose to explain some concepts.
The error you will see in the console on the host is as follows.

As we are not sharing any libraries, @angular/core library is going to get loaded twice in the browser. And as we know, Angular does not work well with multiple versions loaded in the browser. To solve this issue, we need to share the core library between apps.
On both the host and the remote, add the following in the Webpack’s shared config that we left empty. Restart your apps and try again.
shared: {
"@angular/core": {
singleton: true,
strictVersion: true,
requiredVersion: "^17.1.0",
},
},and voila! Our apps will work after this. It is also advisable to share the other angular libraries as well. Hence, I am going to share the router and common as well to be fail-safe.
shared: {
"@angular/core": {
singleton: true,
strictVersion: true,
requiredVersion: "^17.1.0",
},
"@angular/common": {
singleton: true,
strictVersion: true,
requiredVersion: "^17.1.0",
},
"@angular/router": {
singleton: true,
strictVersion: true,
requiredVersion: "^17.1.0",
},
}Thank you for reading this long article. Hopefully, you will have learned something new today.
Please subscribe and stay tuned for the next articles.




