How To Build Micro Frontends Using Module Federation in Angular
How To Build Micro Frontends Using Module Federation in Angular
Alisa Duncan
Enter micro-frontend architecture. Micro frontends are modeled after the same concept as
microservices, as a way to decompose monolithic frontends. You can combine micro-sized
frontends to form a fully-featured web app. Since each micro frontend can be developed and
deployed independently, you have a powerful way of scaling out frontend applications.
So what does the micro-frontend architecture look like? Let’s say you have an e-commerce
site that looks as stunning as this one:
You might have a shopping cart, account information for registered users, past orders,
payment options, etc. You might be able to further categorize these features into domains,
each of which could be a separate micro frontend, also known as a remote . The collection
of micro-frontend remotes is housed inside another website, the host of the web
application.
So, your e-commerce site using micro frontends to decompose different functionality might
look like this diagram, where the shopping cart and account features are in their separate
routes within your Single Page Application (SPA):
You might be saying, “Micro frontends sound cool, but managing the different frontends and
orchestrating state across the micro frontends also sounds complicated.” You’re right. The
concept of a micro frontend has been around for a few years and rolling your own micro-
frontend implementation, shared state, and tools to support it was quite an undertaking.
However, micro frontends are now well supported with Webpack 5 and Module Federation.
Not all web apps require a micro-frontend architecture, but for those large, feature-rich web
apps that have started to get unwieldy, the first-class support of micro frontends in our web
tooling is definitely a plus.
This post is part one in a series where we’ll build an e-commerce site using Angular and micro
frontends. We’ll use Webpack 5 with Module Federation to support wiring the micro frontends
together. Then we’ll demonstrate sharing authenticated state between the different frontends,
and deploy it all to a free cloud hosting provider.
In this first post, we’ll explore a starter project and understand how the different apps connect,
add authentication using Okta, and add the wiring for sharing authenticated state. In the end,
you’ll have an app that looks like this:
Prerequisites
Node This project was developed using Node v16.14 with npm v8.5
Angular CLI
Okta CLI
Table of Contents
Clone the Angular Micro Frontend Example GitHub repo by following the steps below and
open the repo in your favorite IDE.
// ...imports here
module.exports = {
// ...other very important config properties
plugins: [
new ModuleFederationPlugin({
library: { type: "module" },
shared: share({
// ...important external libraries to share
...sharedMappings.getDescriptors()
})
}),
sharedMappings.getPlugin()
],
};
In the webpack.config.js for mfe-basket , you’ll see the path for @shared at the
top of the file and the configuration to identify what to expose in the remote application.
The shell application serves on port 4200, and the mfe-basket application serves on
port 4201. We can open up two terminals to run each application, or we can use the following
npm script created for us by the schematic to add
@angular-architects/module-federation :
When you do so, you’ll see both applications open in your browser and how they fit together
in the shell application running on port 4200. Click the Basket button to navigate to a new
route that displays the BasketModule in the mfe-basket application. The sign-in button
doesn’t work quite yet, but we’ll get it going here next.
Note - Another option I could have used for the starter is a Nx workspace. Nx has great tooling
and built-in support for building micro frontends with Webpack and Module Federation. But I
wanted to go minimalistic on the project tooling so you’d have a chance to dip your toes into
some of the configuration requirements.
The @shared syntax might look a little unusual to you. You may have expected to see a
relative path to the library. The @shared syntax is an alias for the library’s path, which is
defined in the project’s tsconfig.json file. You don’t have to do this. You can leave
libraries using the relative path, but adding aliases makes your code look cleaner and helps
ensure best practices for code architecture.
Because the host application doesn’t know about the remote applications except in the
webpack.config.js , we help out the TypeScript compiler by declaring the remote
application in decl.d.ts . You can see all the configuration changes and source code made
for the starter in this commit.
Before you begin, you’ll need a free Okta developer account. Install the Okta CLI and run
okta register to sign up for a new account. If you already have an account, run
okta login . Then, run okta apps create . Select the default app name, or change it
as you see fit. Choose Single-Page App and press Enter.
Make a note of the Issuer and the Client ID . You’ll need those values here soon.
We’ll use the Okta Angular and Okta Auth JS libraries to connect our Angular application with
Okta authentication. Add them to your project by running the following command.
Next, we need to import the OktaAuthModule into the AppModule of the shell
project and add the Okta configuration. Replace the placeholders in the code below with the
Issuer and Client ID from earlier.
import { OKTA_CONFIG, OktaAuthModule } from '@okta/okta-angular';
import { OktaAuth } from '@okta/okta-auth-js';
@NgModule({
...
imports: [
...,
OktaAuthModule
],
providers: [
{ provide: OKTA_CONFIG, useValue: { oktaAuth } }
],
...
})
After authenticating with Okta, we need to set up the login callback to finalize the sign-in
process. Open app-routing.module.ts in the shell project and update the routes
array as shown below.
Now that we’ve configured Okta in the application, we can add the code to sign in and sign
out. Open app.component.ts in the shell project. We will add the methods to sign in
and sign out using the Okta libraries. We’ll also update the two public variables to use the
actual authenticated state. Update your code to match the code below.
import { Component, Inject } from '@angular/core';
import { filter, map, Observable, shareReplay } from 'rxjs';
import { OKTA_AUTH, OktaAuthStateService } from '@okta/okta-angular';
import { OktaAuth } from '@okta/okta-auth-js';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styles: []
})
export class AppComponent {
public isAuthenticated$: Observable<boolean> = this.oktaStateService.authState$
.pipe(
filter(authState => !!authState),
map(authState => authState.isAuthenticated ?? false),
shareReplay()
);
We need to add the click handlers for the sign-in and sign-out buttons. Open
app.component.html in the shell project. Update the code for Sign In and Sign Out
buttons as shown.
<li>
<button *ngIf="(isAuthenticated$ | async) === false; else logout"
class="flex items-center transition ease-in delay-150 duration-300 h-10 px-4 rounded-l
(click)="signIn()"
>
<span class="material-icons-outlined text-gray-500">login</span>
<span> Sign In</span>
</button>
<ng-template #logout>
<button
class="flex items-center transition ease-in delay-150 duration-300 h-10 px-4 round
(click)="signOut()"
>
<span class="material-icons-outlined text-gray-500">logout</span>
<span> Sign Out</span>
</button>
</ng-template>
</li>
Try running the project using npm run run:all . Now you’ll be able to sign in and sign out.
And when you sign in, a new button for Profile shows up. Nothing happens when you click it,
but we’re going to create a new remote, connect it to the host, and share the authenticated
state here next!
You’ll now create a component for the default route, HomeComponent , and a module to
house the micro frontend. We could wire up the micro frontend to only use a component
instead of a module. In fact, a component will cover our needs for a profile view, but we’ll use
a module so you can see how each micro frontend can grow as the project evolves. Run the
following two commands in the terminal:
Let’s update the code. First, we’ll add the default route. Open
projects/mfe-profile/src/app/app-routing.module.ts and add a new route
for HomeComponent . Your route array should match the code below.
Finally, we can update the code for the profile. Luckily, Angular CLI took care of a lot of the
scaffolding for us. So we just need to update the component’s TypeScript file and the
template.
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styles: []
})
export class ProfileComponent {
public profile$ = this.oktaStateService.authState$.pipe(
filter(state => !!state && !!state.isAuthenticated),
map(state => state.idToken?.claims)
);
Next, open the corresponding template file and replace the existing code with the following:
1. Updates the project’s angular.json config file to add the port for the application and
updates the builder to use a custom Webpack builder
2. Creates the webpack.config.js files and scaffolds out default configuration for
Module Federation
First, let’s add the new micro frontend to the shell application by updating the
configuration in projects/mfe-profile/webpack.config.js . In the middle of the file,
there’s a property for plugins with commented-out code. We need to finish configuring
that. Since this application is a remote, we’ll update the snippet of code under the comment:
The defaults are mostly correct, except we have a module, not a component that we want to
expose. If you want to expose a component instead, all you’d do is update which component
to expose. Update the configuration snippet to expose the ProfileModule by matching
the following code snippet:
Now we can incorporate the micro frontend in the shell application. Open
projects/shell/webpack.config.js . Here is where you’ll add the new micro
frontend so that the shell application knows how to access it. In the middle of the file,
inside the plugins array, there’s a property for remotes . The micro frontend in the starter
code, mfeBasket , is already added to the remotes object. You’ll also add the remote for
mfeProfile there, following the same pattern but replacing the port to 4202. Update your
configuration to look like this.
We can update the code to incorporate the profile’s micro frontend. Open
projects/shell/src/app/app-routing.module.ts . Add a path to the profile micro
frontend in the routes array using the path ‘profile’. Your routes array should look like this.
What’s this!? The IDE flags the import path as an error! The shell application code doesn’t
know about the Profile module, and TypeScript needs a little help. Open
projects/shell/src/decl.d.ts and add the following line of code.
<a routerLink="/profile">
This is everything we need to do to connect the micro-frontend remote to the host application,
but we also want to share authenticated state. Module Federation makes sharing state a piece
of (cup)cake.
There are two places to add shared code. One place is for code implementation within the
project, and one is for shared external libraries. In this case, we can share the Okta external
libraries as we didn’t implement a service that wraps Okta’s auth libraries, but I will point out
both places.
First, we’ll add the Okta libraries. Scroll down towards the bottom of the file to the shared
property. You’ll follow the same pattern as the @angular libraries already in the list and add
the singleton instances of the two Okta libraries as shown in this snippet:
shared: share({
// other Angular libraries remain in the config. This is just a snippet
"@angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@okta/okta-angular": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@okta/okta-auth-js": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
...sharedMappings.getDescriptors()
})
When you create a library within this project, like the basket service and project service in the
starter code, you add the library to the sharedMappings array at the top of the
webpack.config.js file. If you create a new library to wrap Okta’s libraries, this is where
you’d add it.
Now that you’ve added the Okta libraries to the micro-frontend host, you need to also add
them to the remotes that consume the dependencies. In our case, only the mfe-profile
application uses Okta authenticated state information. Open
projects/mfe-profile/webpack.config.js . Add the two Okta libraries to the
shared property as you did for the shell application.
Now, you should be able to run the project using npm run run:all , and the cupcake
storefront should allow you to log in, see your profile, log out, and add items to your cupcake
basket!
Next steps
I hope you enjoyed this first post on creating an Angular micro-frontend site. We explored the
capabilities of micro frontends and shared state between micro frontends using Webpack’s
Module Federation in Angular. You can check out the completed code for this post in the
local branch in the @oktadev/okta-angular-microfrontend-example GitHub repo by using
the following command:
In the next post I’ll show how to prepare for deployment by transitioning to dynamic module
loading and deploying the site to a free cloud provider. Let’s continue building the site by
reading “Secure and Deploy Micro Frontends with Angular”!
Don’t forget to follow us on Twitter and subscribe to our YouTube channel for more exciting
content. We also want to hear from you about what tutorials you want to see. Leave us a
comment below.
Alisa Duncan
Alisa Duncan is a Senior Developer Advocate at Okta, a full-stack developer, and a community
builder who loves the thrill of learning new things. She is a Google Developer Expert in
Angular and organizes coding workshops and community events locally and internationally.
Her background is primarily working on enterprise software platforms, and she is a fan of all
things TypeScript and JavaScript.
Need Support?
You can reach us directly at developers@okta.com or you can also ask us on the forum.
OKTA.COM
HELP CENTER
TRUST
C O N TAC T & L E G A L
MORE INFO
Pricing
Integrate with Okta
Change Log
3rd-party notes
Auth0 platform