How To Build Micro Frontend Based On Angular?
In the current digital age, online applications are becoming bigger and more complex, which necessitates the use of different teams to manage them. In certain cases, it may be wiser to release just a portion of your web application into production rather than the complete program at once.
The majority of today’s high-end software is client-side only, making it more difficult to maintain. Other challenges arise when dealing with a large monolithic web application.
A micro-frontend architecture using Angular development components is becoming more and more successful as applications get more sophisticated, demanding on-the-fly scalability and great responsiveness.
Using the micro frontend angular approach, each part of a website or online application is managed by a different team. Each team is focused on a single area of the company or a single goal. Functionality is created from server to user interface by a cross-functional team.
Pros of Micro Frontend Architecture
A CI/CD workflow that is automated
The CI/CD workflow is made easier by the fact that each app integrates and deploys separately. You don’t have to worry about the whole application when you introduce a new feature since all functions are distinct. The whole construction process will be halted if a minor error is found in a module’s code.
Flexibility of team
Several systems may benefit from the flexibility of multiple teams operating independently.
Single responsibility
Using this method, each team may focus on a specific task while creating its components. For any angular micro frontend team, functionality is the only consideration.
Reusability
You’ll be able to use the same code many times. Multiple teams might benefit from reusing a single module that has been developed and delivered.
Simple learning
A monolithic design with a large code structure is more difficult for new developers to learn and comprehend than smaller parts.
Consolidation of the modules for the header and footer
A minimum of two components are available for export from this module in this part:
To begin, we must create a new app and configure an angular builder specifically for it (this builder allow us to use custom web pack configs)
“` ng new layout npm i –save-dev ngx-build-plus “`
It’s now time to set up webpack configuration files at the root of the project, which is located in a /webpack/config/js directory.
JavaScript
1 // webpack.config.js
2 const webpack = require(“webpack”);
3 const ModuleFederationPlugin =require(“webpack/lib/container/ModuleFederationPlugin”);
4
5 module.exports = {
6 output: {
7 publicPath: “https://localhost:4205/”,
8 uniqueName: “layout”,
9 },
10 optimization: {
11 runtimeChunk: false,
Because we can share common npm packages across various frontends via module federation, lazy-loaded chanks will have less payload to carry. We are able to set up the minimal needed version, as well as several versions of a single package being permitted. etc.,
We have an exposed area, thus here we can specify which elements we need to export from the micro-frontend architecture angular application. Currently, we export only two components in this situation.
A custom angular.json configuration file and the ngx-build-plus default builder must then be added.
JavaScript
1{
2 …
3 “projects”: {
4 “layout”: {
5 “projectType”: “application”,
6 “schematics”: {
7 “@schematics/angular:component”: {
8 “style”: “scss”
9 },
10 “@schematics/angular:application”: {
11 “strict”: true
The federation registration page
The login/register page logic will be included in this web development application. We’ll need to create a new app and install a custom builder to use custom webpack configs in the same way as before.
“` ng new registerPage npm i –save-dev ngx-build-plus “`
To complete the process, we’ll need to build webpack-config.js and webpack-prod-config-js files.
JavaScript
1 // webpack.config.js
2 const webpack = require(“webpack”);
3
4 const ModuleFederationPlugin = require(“webpack/lib/container/ModuleFederationPlugin”);
5
6 module.exports = {
7 output: {
8 publicPath: “https://localhost:4201/”,
9 uniqueName: “register”,
10 },
11 optimization: {
As you can see, just RegisterPageModule is exported here. We may use this module as a “lazy-loaded” shell app module. It’s also essential that we change the default builder to ngx and add webpack configurations to the angular JSON file (the same as we did for the Header & Footer module before).
Federation of Dashboard Modules
An authorised user may see the information in this module. Using own webpack configs, the same procedure as with the Register page:
JavaScript
1 // webpack.config.js
2 const webpack = require(“webpack”);
3 const ModuleFederationPlugin = require(“webpack/lib/container/ModuleFederationPlugin”);
4
5 module.exports = {
6 output: {
7 publicPath: “https://localhost:4204/”,
8 uniqueName: “dashboard”,
9 },
10 optimization: {
11 runtimeChunk: false,
Federation of Shell Module Apps
The main app from which all of the smaller frontend modules are loaded. An angular app builder is used to construct a new app, the same as previously.
“` ng new shell npm i –save-dev ngx-build-plus “`
Customize the webpack configuration by adding your own:
JavaScript
1 // webpack.config.js
2 const webpack = require(“webpack”);
3 const ModuleFederationPlugin = require(“webpack/lib/container/ModuleFederationPlugin”);
4
5 module.exports = {
6 output: {
7 publicPath: “https://localhost:4200/”,
8 uniqueName: “shell”,
9 },
10 optimization: {
11 runtimeChunk: false,
Angular.json has to be configured with a custom builder for webpack before we can use it.
We define all module settings in environment/environment.ts (for the prod version we need to change the localhost address with the deployed public address):
JavaScript
1 export const environment = {
2 production: false,
3
4 microfrontends: {
5 dashboard: {
6 remoteEntry: ‘https://localhost:4204/remoteEntry.js’,
7 remoteName: ‘dashboard’,
8 exposedModule: [‘DashboardModule’],
9 },
10
11 layout: {
Next, a loading Dashboard and registration page must be added wherever they are needed. First, we need to construct utility functions for module federation, which allow us to import remote modules from other applications.
JavaScript
1// src/app/utils/federation-utils.ts
2 type Scope = unknown;
3 type Factory = () => any;
4 interface Container {
5 init(shareScope: Scope): void;
6 get(module: string): Factory;
7}
8 declare const __webpack_init_sharing__: (shareScope: string) => Promise;
9 declare const __webpack_share_scopes__: { default: Scope };
10 const moduleMap: Record<string, boolean> = {};
11 function loadRemoteEntry(remoteEntry: string): Promise {
And utils for building lazy loaded routes:
JavaScript
1
2 // src/app/utils/route-utils.ts
3 import { loadRemoteModule } from ‘./federation-utils’;
4 import { Routes } from ‘@angular/router’;
5 import { APP_ROUTES } from ‘../app.routes’;
6 import { Microfrontend } from ‘../core/services/microfrontends/microfrontend.types’;
7
8 export function buildRoutes(options: Microfrontend[]): Routes {
9 const lazyRoutes: Routes = options.map((o) => ({
10 path: o.routePath,
11 loadChildren: () => loadRemoteModule(o).then((m) => m[o.ngModuleName])
Then we need to define a micro frontend with angular service:
JavaScript
1
2 // src/app/core/services/microfrontends/microfrontend.service.ts
3 import { Injectable } from ‘@angular/core’;
4 import { Router } from ‘@angular/router’;
5 import { MICROFRONTEND_ROUTES } from ‘src/app/app.routes’;
6 import { buildRoutes } from ‘src/app/utils/route-utils’;
7
8 @Injectable({ providedIn: ‘root’ })
9 export class MicrofrontendService {
10 constructor(private router: Router) {}
11
And file for type:
JavaScript
1
2 // src/app/core/services/microfrontends/microfrontend.types.ts
3 import { LoadRemoteModuleOptions } from “src/app/utils/federation-utils”;
4
5 export type Microfrontend = LoadRemoteModuleOptions & {
6 displayName: string;
7 routePath: string;
8 ngModuleName: string;
9 canActivate?: any[]
10};
Then we need to declare remote modules according to the routes:
JavaScript
1
2 // src/app/app.routes.ts
3 import { Routes } from ‘@angular/router’;
4 import { LoggedOnlyGuard } from ‘./core/guards/logged-only.guard’;
5 import { UnloggedOnlyGuard } from ‘./core/guards/unlogged-only.guard’;
6 import { Microfrontend } from ‘./core/services/microfrontends/microfrontend.types’;
7 import { environment } from ‘src/environments/environment’;
8
9 export const APP_ROUTES: Routes = [];
10
11 export const MICROFRONTEND_ROUTES: Microfrontend[] = [
And use the Micro Frontend architecture service in the main app module:
JavaScript
1 // src/app/app.module.ts
2 import { APP_INITIALIZER, NgModule } from ‘@angular/core’;
3 import { BrowserModule } from ‘@angular/platform-browser’;
4 import { RouterModule } from ‘@angular/router’;
5
6 import { AppRoutingModule } from ‘./app-routing.module’;
7 import { AppComponent } from ‘./app.component’;
8 import { APP_ROUTES } from ‘./app.routes’;
9 import { LoaderComponent } from ‘./core/components/loader/loader.component’;
10 import { NavbarComponent } from ‘./core/components/navbar/navbar.component’;
11 import { MicrofrontendService } from ‘./core/services/microfrontends/microfrontend.service’;
Now we need to load the Footer and Header components. For that we need to update the app component:
JavaScript
1 // src/app/app.component.html
2
3
4
5
6
7
8
9
10
11
And file src/app/app.component.ts will look like this:
JavaScript
1 import {
2 ViewContainerRef,
3 Component,
4 ComponentFactoryResolver,
5 OnInit,
6 AfterViewInit,
7 Injector,
8 ViewChild
9 } from ‘@angular/core’;
10 import { RouteConfigLoadEnd, RouteConfigLoadStart, Router } from ‘@angular/router’;
11 import { loadRemoteModule } from ‘./utils/federation-utils’;
Here we have logic for loaders and logic for lazy-loaded components (Header, Footer).
Communication between Micro Frontends
Different micro frontends may communicate with one other by using a variety of methods. Here are some more specifics. We’ve opted to utilize Custom Event to communicate with our customers. Sending custom data through Event payload is possible with the Custom Event feature.
One module should dispatch custom events like this:
JavaScript
1 const busEvent = new CustomEvent(‘app-event-bus’, {
2 bubbles: true,
3 detail: {
4 eventType: ‘auth-register’,
5 customData: ‘some data here’
6 }
7 });
8 dispatchEvent(busEvent);
Other modules can subscribe to this event:
JavaScript
1
2 onEventHandler(e: CustomEvent) {
3 if (e.detail.eventType === ‘auth-register’) {
4 const isLogged = Boolean(localStorage.getItem(‘token’));
5 this.auth.isLogged = isLogged;
6 if (isLogged) {
7 this.router.navigate([‘/’]);
8 } else {
9 this.router.navigate([‘/signup’]);
10 }
11 }
Conclusion
As frontend codebases get more complex, there is a growing desire for more manageable micro frontend designs. In order to expand software delivery across separate, autonomous teams, it’s critical to be able to set clear boundaries that provide the correct amounts of coupling and cohesiveness between technical and domain entities.