Angular micro-frontend architecture. Part 2/3 — Installing the Nx monorepo and creating micro-frontend apps.

Denis Khrunov
9 min readMar 30, 2022

--

Hello everyone! 👋

In this series of posts I will try to show with a real example how you can use the micro-frontend architecture (hereinafter abbreviated as MFE) in an Angular application. For implementation, we need libraries ngx-mfe, which depends on @angular-architects/module-federation.

If you find an error, please leave a note or leave a comment.

This is post 2 of 3 in the series “Angular micro-frontend architecture”

Preface

It is not necessary to use Nx monorepo, you can also use the standard approach to create a monorepo in Angular with angular.json and projects. But Nx offers a lot more options.More about Nx here.

If you choose not to use Nx, you can create Host and Remote applications using the builder provided in the @angular-architects/module-federation npm package.

To create a Host app (shell) and a Remote app (mfe1) use the following commands in Angular project dir:

ng add @angular-architects/module-federation --project shell --port 5000ng add @angular-architects/module-federation --project mfe1 --port 3000

Full code of this part can be find here 🔥.

Create an Nx Workspace

Run the command below to create Nx monorepo:

yarn create nx-workspace WORKSPACE_NAME --packageManager=yarn

Chose an empty workspace

Choose don’t use Nx Cloud

Done

Add Angular to newly created Nx monorepo

After you have successfully created Nx monorepo don’t forget to change to the created directory.

cd WORKSPACE_NAME

To install, you need to run the following command in workspace dir:

yarn add -D @nrwl/angular

Easy! You can now use Nx Generators to generate the Angular apps and libs.

Create Host and Remote micro-frontend apps

We will start with the Shell app which will act as a host application for the MFE:

yarn nx g @nrwl/angular:app shell --mfe --mfeType=host --port=4200 --routing=true

Now, let’s create the AddressForm app as a remote application:

yarn nx g @nrwl/angular:app address-form --mfe --mfeType=remote --port=4201 --routing=true
  • --mfeType option specifies the application type — Host or Remote.
  • --port option specifies the port on which the application will run locally. it will save us from the chance of multiple remote apps trying to run on the same port.

After running this command, you create the two new application in the apps folder address-form and shell.

File structure

And in the workspace.json file, we have two applications we created:

workspace.json

Сarefully: if you create a Remote app and don’t import the MFE module, then TS will not be able to find this module at compile time and it will not be included in the built bundle.

The RemoteEntryModule generated will be imported in app.module.ts file, however, it is not used in the AppModule itself. This it to allow TS to find the Module during compilation, allowing it to be included in the built bundle.

This is required for the Module Federation Plugin to expose the Module correctly.

Imported RemoteEntryModule to AppModule in address-form MFE

You can choose to import the MFE module in the AppModule if you wish, however, it is not necessary.

These two commands created the following files:

  • Created the standard Angular application files.
  • Created for each app webpack.config.js files, that configured for working with Module Federation.
  • Added a bootstrap.ts file, moved the code that is normally in main.ts to bootstrap.ts. And changed main.ts to dynamically import bootstrap.ts (this is required for the Module Federation to correct load versions of shared libraries)
  • Installed Manfred Steyer’s @angular-architects/module-federation package (Read more on it here)

Overview the Address Form app (Remote)

Let’s start with the Address Form which would also be called the Remote in terms of Module Federation. This is a micro-frontend app that will be used in the host application.

Let’s see what was generated in the application:

  • The auto-generated RemoteEntryModule is the module that will be available for loading at run time in the host application.
    This is common Angular component which is declared in the RemoteEntry module.
RemoteEntryComponent file of the AddressForm MFE
The RemoteEntryModule file of the AddressForm MFE
  • We also have AppComponent and AppModule as standard Angular app. app.component.ts and app.component.html are almost empty, AppComponent has <router-outlet> tag inside template.
The AppModule file of the AddressForm MFE

As you can see RemoteEntryModule, is imported but not used in AppModule, why it is so described here.

  • project.json, this file contains configuration for application like angular.json file. The screenshot below shows the command to launch the AddressForm app and the port on which the application will be launched, in our case it is port 4201.
The project.json file of the AddressForm MFE
  • And last but not least, this is the webpack.config.js file. We see the following within webpack configuration:
The webpack.config.js file of the AddressForm MFE

We are only interested in the setting inside the ModuleFederationPlugin.

Taking a look at each property of the ModuleFederationPlugin configuration in turn:

  • name is the name that Webpack assigns to the Remote app. It usually matches the name of the app.
  • filename is the name given to the Remote entrypoint that Webpack sets up to allow Host apps to consume the remote application. By convention, this file is usually called remoteEntry.js.
  • exposes is the list of source files that the Remote app provides consuming Host apps for their own use.
  • shared is a list of libraries that should be shared between the Remote and the Host app. By setting singleton: true we ensure that Webpack will only provide one instance of the library across the Host app and the Remote application.
    More details about how Module Federation deals with different versions can be found in this article.

In our example we sets of the Remote app name as “address-form”, entrypoint as remoteEntry.js file, and provide access to our RemoteEntryModule by the “./Module” key.

We also write shared libraries (all @angular libs and rxjs) that will be shared between Host and Remote application inside builds.

Overview the Shell app (Host)

Shell is a normal Angular app, with the only difference being that it has webpack.config.js:

The webpack.config.js file of the Shell app

In webpack.config.js file of Remote app the key difference is we can configure remotes object. This is where you list the remote applications you want to consume in your host application.

You give it a name that you can reference in your code, in this case address-form.
Then you assign it a string value of the following pattern: {url}/{remoteEntrypointFilename}. Example http://localhost:4201/remoteEntry.js.

  • url is the url where the remote application is hosted.
  • remoteEntrypointFilename is the filename supplied in the remote's webpack configuration.

Now that we have our applications generated, let’s move on to building out some functionality for each.

But you can omit remotes object and don’t configure it. We don’t define any remotes (micro-frontends) upfront but configure the packages we want to share with the remotes we get informed about at runtime this is Dynamic Module Federation.

Personally, I prefer to use Dynamic Module Federation.

Adding Angular Material to Apps

We’ll start by adding Angular Material to our two apps.

yarn add @angular/material
Adding Angular Material to workspace

Add theming to root styles.scss in Shell and AddressForm apps:

@import "~@angular/material/prebuilt-themes/indigo-pink.css";

And finally BrowserAnimationsModule to app.module.ts in every apps.

Creating and exposes form component in AddressForm MFE

First create new module:

nx g @nrwl/angular:module form --project=address-form

Then create the address form component with angular material schematics:

nx generate @angular/material:addressForm --name=form --project=address-form --style=scss --changeDetection=OnPush --skipTests

Don’t forget import FormModule to app.component.ts so that TS can find the given module during compile.

Now let’s configure the ModuleFederationPlugin in webpack.config.js

Delete the unnecessary module “./Module” from exposes object and remove folder /src/app/remote-entry, after add new line with our new FormModule like that:

The webpack.config.js file of the AddressForm MFE

We are done with setting up the Remote app, now we can move on to setting up the Host app!

The following Routing steps are optional and are required to configure access to this component when running this micro-frontend as a standalone application:

Configure routing inside FormModule like this:

The FormModule file of the AddressForm MFE

And configure routing inside AppModule like this:

The AppModule file of the AddressForm MFE

Run AddressForm MFE:

nx run address-form:serve:development

Now our FormComponent available at htpp://localhost:4201/form.

Our created form

Link a Remote app with a Host app

To dynamically load a micro-frontend at runtime, we can use the helper function loadRemoteModule provided by the @angular-architects/module-federation plugin:

The app.module.ts file inside Shell app (Host)

Let’s run our Shell and AddressFrom apps:

nx serve shellnx serve address-form

Or you can use another command to run all your apps:

nx run-many --target=serve --all=true

If go to htpp://localhost:4200/address-form, then you will see at the bottom of the page our form component as MFE.

The Shell app. Open by htpp://localhost:4200/address-form

And in the network panel in Chrome you find that file remoteEntry.js , that will be loaded on demand. If you search in the file, you can find our compiled FormModule.

The network panel with remoteEntry.js file

Conclusion

As you can see, with this approach, your AddressForm application can be deployed independently and developed independently without forcing you to have to rebuild or redeploy your Shell application. This can lead to a powerful micro-frontend architecture that enables multiple teams to work independently in a single monorepo!

In this tutorial, we exposed a single module that was consumed dynamically as an Angular Route.

If this article was helpful to you, clap 👏 , thanks!

--

--

Denis Khrunov

I’m a Front-end Typescript Developer from Russia. I am engaged in front-end development, the main stack of Angular 2+ and in my free time I keep learning.