Insights Gained from Employing Micro-frontends Architecture in Angular

Mona Elsayed
ELCA IT
Published in
6 min readJun 13, 2023

This article will cover some of the ideas for implementing small enterprise micro-frontends architecture projects using Angular and Module Federation (@angular-architects/module-federation). While I won’t delve into the pros and cons of micro-frontend architecture compared to other architectures or when to use them, I assume that you are already familiar with or working on a micro-frontend architecture.

This article will incorporate the terms Shell/Host and micro-frontend/Remote, originating from the Module Federation feature introduced in Webpack 5.0. While Module Federation is not the sole method for implementing micro-frontends architecture, it is currently regarded as one of the most prevalent approaches.

Photo by Ross Sneddon on Unsplash

Outline the responsibilities of the Shell and the micro-frontends

Initiating a new project with micro-frontend architecture can initially be mystifying. For instance, you might wonder whether the Shell or the micro-frontend should handle authentication, where to place logging, how to configure the app, and where to store these configurations, among other considerations.

It is vital to define the responsibilities of each part, Shell/micro-frontend, early in the process. Catering to your business needs is equally important, including deciding if the micro-frontend should operate as a standalone app (This will, for instance, affect your architecture regarding displaying your styles). Furthermore, specifying cross-cutting concerns, as well as their handling and location, should be established during the initial phases of application development. In the subsequent sections, we will explore various concepts that could assist you in developing your unique microfrontend architecture.

Use a Backend for every Micro-frontend

Consider each of your Micro apps as distinct domains, and make it a point to develop a specific backend for every micro-frontend. This essentially means creating an individualized backend API for each micro-frontend application. When there are multiple domains, each one can have its own micro-frontend and corresponding backend.

Furthermore, it is beneficial to keep the principles of Bounded Context and Domain Driven Design in mind during the development of both the backend and frontend. These approaches can significantly help in managing complexities in the system and promoting a clearer understanding among different parts of the organization.

To ensure consistency and synchronization, it’s also recommended to maintain the backend API and the frontend application within the same repository. This practice aids in tracking changes and fosters better collaboration among the development teams.

Backend for every Microfrontend
Organize your Teams — Photo by Author (Mona Elsayed)

Carefully share data between micro-frontends

To achieve the goals of independent deployment and development, it is crucial to avoid tightly coupled micro-frontends. Your micro-frontends should be organized and divided according to business subdomains. Extensive data sharing between microfrontends may indicate a data cohesion relationship, and merging those microfrontends could be considered.

Using the router for data sharing is often preferable, as it is a secure and straightforward method. However, for certain scenarios, the router might be inadequate. In these instances, CustomEvents and Event listeners can serve as viable alternatives. As a best practice and to avoid coupling avoid sharing data directly between micro-frontends. Instead, connect the Event bus to the Shell, which will then broadcast the data to other micro-frontends.

The diagram below provides a basic representation of data sharing between microfrontends:

The Shell is my controller
The shell is your controller — Photo by Author (Mona Elsayed)

Implementing an Effective Strategy for Sharing Key Libraries

In the context of micro-frontend architecture, it’s imperative to manage multiple Angular applications, each backed by fundamental libraries such as Angular itself and Angular Material. Deciding on a well-thought-out strategy for sharing these key libraries across different microfrontends and the central shell early in the project is crucial.

When formulating this strategy, take into account the following:

  • The size of your project or team
  • The potential expenses due to inter-team dependencies during upgrades
  • The clarity of the upgrade strategy for significant, minor, and patch releases
  • The loading mechanism for your micro-frontends — will they be dynamically loaded, or will the shell preload specific micro-frontends?

Post these considerations, you could look at exploiting the “singleton,” “strictVersion,” and “requiredVersion” attributes provided by @angular-architects/module-federation. The most straightforward approach is to set singleton=true, strictVersion=true, and requiredVersion=auto. This forces all teams to operate on the same version of key libraries such as Angular or Angular Material. However, while maintaining consistency, this method may limit flexibility as teams become interdependent. For instance, if an upgrade is required from Angular 15 to 16, and you have three teams managing four micro-frontends, the upgrade and deployment process would have to wait until all teams are ready to simultaneously upgrade their versions.

Alternatively, a flexible solution would be to allow teams to upgrade key libraries independently, without the constraints of strictVersion or singleton. This method affords teams the luxury of deploying independently, which is one of the main advantages of the micro-frontends architecture. Emphasizing this aspect in your strategy can help teams work more efficiently with major libraries like Angular and Angular Material.

Use forRoot/forChild Pattern when possible

To manage service injection in your micro-frontend architecture, consider using the forRoot/forChild pattern. Think of your Shell as the root and the microfrontend as the child. Whenever you use a module with forRoot/forChild static methods, it’s best to use them accordingly.

For example, the ngx-translate library has forRoot and forChild methods. Use the forRoot in the Shell to instantiate services as singletons only once throughout the application’s lifetime. Then, use the forChild in each micro-frontend to use those singletons and instantiate only the required services for that specific micro-frontend. By using this pattern, you can efficiently manage your service injection and improve the performance of your microfrontend architecture.

Don’t expose your AppModule

Angular supports a single AppModule, so in case you need to expose more than one module exposing the AppModule would be problematic. AppModule should only be employed when launching a micro-frontend as a self-contained application with a distinct URL (Mini Shell) or for testing purposes.

Rather than exposing the AppModule of a micro-frontend within the Shell, establish a separate <Domain>Module for each micro-frontend. For instance, if managing payments is the responsibility of a micro-frontend, create a PaymentModule and expose it in the Shell. This approach promotes an organized, modular structure and enhances scalability and maintainability in the long term. Ideally, each microfrontend should contain one or multiple modules.

Let’s consider a scenario where your application is segmented into three main domains, with one functioning as a separate, standalone application. Cross-cutting concerns like logging, security, and exception handling need to be universally accessible, both for the central shell and the independent application. Here’s a rudimentary guide on how to architect your Angular application to avoid exposing your app module unnecessarily and to fulfill the specified requirements.

Architect your Angular Solution
Architect your Angular Solution — Photo by Author (Mona Elsayed)

Summary

In architectural decisions, there isn’t a universally right or wrong approach; the ideas mentioned earlier may work well for some projects but not for all. Adhering to these best practices can promote enhanced modularity, scalability, and maintainability while implementing micro-frontend architecture with Angular and Module Federation.

References

--

--

Mona Elsayed
ELCA IT
Writer for

Lead Engineer at ELCA with 15 years' experience, sharing expertise in dotnet, angular, microfrontends, and architecture https://www.linkedin.com/in/elsayedmona