Here I will go over the alternative approach of designing huge applications. The end goal is to show how to substitute the monolithic architecture with micro frontends.
I picked an admin dashboard as an example of an application that we will split into independent apps. But first, a quick overview of why micro frontends will be a great solution in such a case compared with monolithic architecture:
Monolithic
Admin dashboards usually contain a lot of components and complex logic. At some point, it becomes very difficult to maintain and scale the product. Besides that, it makes the integration of new developers quite challenging. Cause new joiners should spend a lot of time understanding and analyzing that huge monolith.
Micro Front-ends
This approach helps to split logic into smaller pieces. In that case, it's much easier to maintain and analyze the codebase. Moreover, responsibility for components can be delegated to different teams. So they can do independent development and deployment of these pieces. That makes micro front ends stand out.
Demo App Architecture
We need to create a workspace that will contain all the projects (components). The next step is the creation of a component wrapper, it is going to be responsible for the navigation of independent pieces and hosting them. The last part is to create micro frontends. That is all. Before we start, let me introduce the tool which is necessary to achieve the goal:
Webpack 5 Module Federation
Here is documentation of it. Briefly, this is the main thing in my example. That plugin will allow loading front-end applications one into another. Let's proceed with implementation and see how it works.
Implementation
As I mentioned above, in the beginning, we need to create the workspace, which is going to hold all projects. For that we need to run the following command:
ng new dashboard-mf --createApplication="false"
After that, the host application has to be created, which is going to be a dashboard wrapper.
ng g application dashboard --routing
ng g c home --project=dashboard
Also, we added a home component to the dashboard. Time to create the first micro front end:
ng g application chart1 --routing
Then it is needed to create a module inside that application that will be lazy-loaded to the host component, plus we can create a component that will display some demo content:
ng g m bar --routing --project=chart1
ng g c bar --project=chart1
Setting up routing in the newly created application:
dashboard-mf/projects/chart1/src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{
path: 'chart1',
loadChildren: () => import('./bar/bar.module').then((m) => m.BarModule)
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
dashboard-mf/projects/chart1/src/app/bar/bar-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {BarComponent} from "./bar.component";
const routes: Routes = [
{
path: '',
component: BarComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class BarRoutingModule { }
Then styling and some HTML content:
dashboard-mf/projects/chart1/src/app/bar/bar.component.html
<section>
<span>Statistics bar chart content</span>
</section>
section {
width: 100%;
height: 100%;
background-color: #0088cc0f;
display: flex;
justify-content: center;
align-items: center;
border: 4px dashed #73089f;
color: #73089f;
font-size: 50px;
font-weight: bold;
}
We have almost everything ready to tie up the host app with the micro front end. At that point, the Module federation comes. That plugin should be installed for both parts:
ng add @angular-architects/module-federation --project dashboard --port 4200
ng add @angular-architects/module-federation --project chart1 --port 5000
Important that both should be running in different ports. After installing a plugin, you can notice that inside the dashboard and chart1 components appeared a webpack configuration file (webpack.config.js). To be able to run everything correctly we need to edit it a bit.
Inside the micro front end app, we need to uncomment code under "For remotes" comment and edit the code below.
name: "chart1",
filename: "chartEntry.js",
exposes: {
'./BarModule': './projects/chart1/src/app/bar/bar.module.ts',
},
The same changes are applicable for the host app, but under the comment "For hosts".
remotes: {
"chart1": "chart1@http://localhost:5000/chartEntry.js"
},
Also, package.json should be updated. The following lines have to be added:
"resolutions": {
"webpack": "^5.4.0",
"license-webpack-plugin": "2.3.17"
},
Let's set up routing in the dashboard component to be able to load independent apps. Furthermore, we will add some content to HTML templates and style a bit a dashboard.
dashboard-mf/projects/dashboard/src/app/app.component.html
<section>
<div class="sidenav">
<a routerLink="/"> <i class="fa fa-home"></i> Home</a>
<a routerLink="/chart1"> <i class="fa fa-signal"></i> Statistics charts</a>
</div>
<div class="mf-content">
<router-outlet></router-outlet>
</div>
</section>
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {HomeComponent} from "./home/home.component";
const routes: Routes = [
{
path: '',
component: HomeComponent,
pathMatch: 'full'
},
{
path: 'chart1',
loadChildren: () =>
import('chart1/BarModule').then((m) => {
return m.BarModule;
})
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
Here we will get an error, cause the dashboard app does not know anything about BarModule, so we need to declare it. For that, I created a file in the root of app type.d.ts, where I declared a module.
dashboard-mf/projects/dashboard/src/app/app.component.scss
@import url(//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css);
section {
width: 100%;
height: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
.mf-content {
width: 83%;
height: 95vh;
padding: 15px;
}
i {
margin-right: 10px;
}
}
.sidenav {
height: 100%;
width: 15%;
position: fixed;
z-index: 1;
top: 0;
left: 0;
background-color: #0088cc;
overflow-x: hidden;
padding-top: 20px;
}
.sidenav a {
padding: 6px 8px 6px 16px;
text-decoration: none;
font-size: 20px;
color: #dddddd;
display: block;
margin-top: 10px;
}
.sidenav a:hover {
color: #ffffff;
}
.main {
margin-left: 160px;
padding: 0px 10px;
}
@media screen and (max-height: 450px) {
.sidenav {padding-top: 15px;}
.sidenav a {font-size: 18px;}
}
dashboard/src/app/home/home.component.html
<section>
<span>Home page content</span>
</section>
dashboard/src/app/home/home.component.scss
section {
width: 100%;
height: 100%;
background-color: #0088cc0f;
display: flex;
justify-content: center;
align-items: center;
border: 4px dashed orange;
color: orange;
font-size: 50px;
font-weight: bold;
}
dashboard-mf/projects/chart1/src/app/bar/bar.component.scss
section {
width: 100%;
height: 100%;
background-color: #0088cc0f;
display: flex;
justify-content: center;
align-items: center;
border: 4px dashed #73089f;
color: #73089f;
font-size: 50px;
font-weight: bold;
}
To run the app, we need to serve dashboard and chrat1 projects. To do that, these commands have to be executed:
ng serve dashboard
ng serve chart1
Here is a quick overview of what we achieve so far. We have a home component that is part of the dashboard wrapper and also we have a link to "Statistics charts", once we click on them, the micro fronted "chart1" gets loaded:
Wrapping up
The goal is achieved, we have created an application that can host another independent app. This is just a simple introduction to how we can deal with micro frontends in Angular applications. This is a kind of template, which still requires a lot of things to be done. This is the first step of developing large applications using micro frontends. I left that example of the template in my GitHub, so feel free to pick it up.
Hope it was useful! Cheers!