adesso Blog

Micro frontends

Generally speaking, backend applications based on a micro-service architecture are complemented by a monolithic application in the frontend, often in the form of a web application. The server-side system receives all the advantages of a distributed architecture, whereas developing the frontend involves a number of well-known challenges. The benefits in terms of scalability, flexibility, organisation and continuous integration and deployment do not extend beyond the boundaries of the backend; instead, they end where the development of the monolithic application begins. The concept of micro frontends aims to solve this problem by dividing frontend web applications into separate, independent subsystems. This creates vertical structures ranging from the user interface to the server-side data logic and persistence.

Vertical structures with micro frontends; source: https://micro-frontends.org/

Vertical structures with micro frontends; source: https://micro-frontends.org/

Pros and cons of micro frontends

Please note that micro frontends are not so much a way to solve technical problems as they are a means of overcoming organisational challenges. Micro frontends offer immense potential, especially when it comes to very large projects that are based on a micro-service architecture in the backend and may even already provide for a functional division of development teams. Here are some of the advantages they offer:

  • Independent development and deployment by autonomous teams
  • Loosely affiliated and simpler code bases
  • Individual teams not bound to specific technologies

However, it is not only the advantages that play a role when contemplating whether to use micro frontends as an architectural template. Like any software development solution, they are not a silver bullet. There are certain disadvantages that come with each of them. These can mainly attributable to increased technical and organisational complexity, since the following aspects will need to be taken into consideration:

  • Splitting up the application into subsystems
  • Consolidating the subsystems to create an overall system
  • Ensuring a consistent user experience
  • Dependency management
  • Higher infrastructure costs

Anyone considering a micro frontend architecture should therefore take the time they need during the planning phase to properly assess the time and costs involved and put themselves in a position where they are able to solve any problems early on.

Technical implementation

If you compare the popularity of micro frontends with that of micro-services, you will find that the former are nowhere as well established as their backend counterparts. This may also be one of the reasons why there are no best practice or go-to approaches on how to implement them. Since the principle behind micro frontends more or less involves little more than developing stand-alone frontend applications separately, deploying them and merging them to create a seamless overall application, the implementation options range from simple native solutions to more complex, tool-based methods. I would like to show how you can implement a micro frontend with the aid of webpack and Module-Federation.

Implementing micro frontends using Angular and webpack’s Module Federation

In version 5 or higher, the webpack module bundler offers a function called Module Federation, which can be used to integrate applications that are bundled and provided separately into another application at runtime. Among other things, the bundler is deployed within the Angular CLI, the standard tool used to develop and deliver Angular applications. For that reason, Module Federation is a great option for implementing a micro frontend architecture within an Angular application landscape. In the section to follow, I would like to show you how to design Angular-based micro frontends with the aid of Module Federation and merge them to create a combined application.

We are looking to implement a prototypical application based on Angular and Module Federation consisting of the following subsystems:

Prototypical application; source: own illustration

Prototypical application; source: own illustration

  • Red: app shell – Angular component as part of the frame application
  • Yellow: ‘Buy’ component – dynamically loaded Angular component
  • Orange: ‘Banner’ component – dynamically loaded web component
  • Green: ‘Cart’ and ‘Menu’ components – part of a dynamically loaded Angular module

The webpack configuration used by the Angular CLI must be modified accordingly in order to use Module Federation. The easiest way to do this is with the help of a custom Angular CLI builder. For this we use the builder that is part of the npm package @angular-architects/module-federation. If we install the package within our existing Angular application as follows: ng add @angular-architects/module-federation --project X -- port X, we get a webpack configuration file that we can customise and which is taken into account by the Angular CLI during the build process. The options --project and -- port transfer the name of the project and the port on which the Angular development server made it available to the project. We can define our micro frontends with the aid of the webpack configuration. Each application that is part of the micro frontend architecture is supplied with its own webpack configuration. This defines what other applications are to be integrated and which parts of the application (modules or components, for example) are to be released externally. The latter can in turn be used by other micro frontends. The following webpack configuration is included as part of the ‘Menu’ application:

	
	module.exports = {
		  output: {
		    uniqueName: "mf1",
		  },
		  plugins: [
		    new ModuleFederationPlugin({
		      name: "menu",
		      filename: "remoteEntry.js",
		      exposes: {
		        "./MenuModule": "./src/app//menu/menu.module.ts",
		      },
		      remotes: {
		        cart: "cart@http://localhost:3001/remoteEntry.js",
		      },
		    }),
		  ],
	

The ‘exposes’ object defines which parts of the application can be released externally and integrated elsewhere. In this case, the ‘Menu’ micro frontend provides a module in the ‘menu.module.ts’ file via the ‘./MenuModule’ key. The ‘remotes’ object defines which other micro frontends are consumed and which URL can be used to integrate them. In this case, the point of entry for the ‘Cart’ micro frontend is referenced via the corresponding URL. The ‘filename’ attribute describes the name of the JavaScript file that, among other things, provides the resolution of the micro frontends and their paths. It is generated by webpack and serves as a point of entry to the individual micro frontends.

The following diagram shows the relationship between the webpack configurations of all sub-applications:

Relationship between individual micro frontends; source: own illustration

Relationship between individual micro frontends; source: own illustration

One way to integrate a micro frontend is using Angular’s lazy loading functionality. To do so, the following route configuration is defined in the app shell:

	
	{
		    path: 'menu',
		    loadChildren: () => import('menu/MenuModule').then((m) => m.MenuModule),
		  },
	

The import path points to the ‘MenuModule’ provided via the ‘Menu’ webpack configuration and appears as follows (example from another application):

Component parts of a dynamic import path; source: own illustration

Component parts of a dynamic import path; source: own illustration

It is also possible to integrate specific individual Angular components, in addition to integrating entire modules. This approach comes in handy if a micro frontend should not represent an entire page of a web application but instead function as part of a higher-level page. One such example would be the ‘Buy’ micro frontend of our system, which renders a button component.

	
	export class BuyTemplateComponent implements OnInit, AfterViewInit {
		  @ViewChild('container', { read: ViewContainerRef })
		  container?: ViewContainerRef;
		  constructor(private cfr: ComponentFactoryResolver) {}
		  ngOnInit(): void {}
		  ngAfterViewInit(): void {
		    this.lazyLoadComponent();
		  }
		  async lazyLoadComponent(): Promise<void> {
		    const { BuyButtonComponent } = await import('buy/BuyComponent');
		    const cFactory = this.cfr.resolveComponentFactory(BuyButtonComponent);
		    this.container?.clear();
		    const buyButtonInstance = this.container?.createComponent(cFactory)
		      .instance;
		    (buyButtonInstance as any).label = 'Buy!';
		  }
		}
	

The ComponentFactoryResolver provided by the framework can be used to render a dynamically loaded micro frontend within an Angular component. To do this, ViewChild() is used to create a reference to a container in the corresponding HTML template and is then employed as an outlet for the component to be rendered. As you can see in line 28, the dynamic import path utilises the same schema used when lazy-loading ‘MenuModule’. Module and component loading is an Angular-specific approach to merging multiple micro frontends. If you want to be less dependent on a framework or frameworks, you can also define individual subsystems with the help of web components and then integrate them into any application you choose. Web components are a web standard, so they can be used both stand-alone and in combination with any modern JavaScript framework.

Conclusion

You have seen how, with a little preparation and by making a few changes to the configuration, you can put together an Angular application out of multiple sub-applications, be they modules or components. The prototype presented here is far less complex than a fully functional application, which requires much more advanced preparation and work from an organisational perspective. Nonetheless, it lays out how you can implement a distributed frontend architecture based on modern web frameworks. Only time will tell whether micro frontends will become a real alternative to the traditional frontend monolith. There are certainly a few projects whose code base and development processes would benefit from making the switch to micro frontends. That said, the barriers to entry in terms of organisational complexity should not be underestimated.

If you would like to learn more about micro-services, please take a look at the following article All About Context! (German).

Would you like to learn more about other exciting topics from the adesso world? Then take a look at our blog posts published so far.

Picture Dario Braun

Author Dario Braun

Dario Braun is a software engineer at adesso and is mainly involved in the development of web applications. With the help of modern frameworks such as Angular, React and NestJS, he develops individual software solutions for customer-specific requirements.

Save this page. Remove this page.