Micro frontends give developers a modern, more nimble approach to application maintenance—especially since Angular works so well for enterprise solutions. Take a look at how to use Angular elements in this post.
Angular is one of the best frameworks for building a beast of an application in the enterprise environment. While arguably one of the more complex frameworks to learn, Angular, which is backed by Google, can build a strong and stable enterprise-grade application with large teams in mind. However, we don’t want to load a giant JavaScript package all at once or we would suffer losses in performance, development and quality.
As the name suggests, our frontend code can now harness the power of micro services, or at least emulate the design pattern. We have seen a transition in the last few years away from classes to functional components, and away from complex REST API or GraphQL to an RPC approach. The Remote Procedure Call is back in style, getting reinvented with new packages like tRPC, and useful once again in modern backend programming. Now we must find a way to break apart our frontend as well in order to maintain our applications with modern approaches.
ng new micro-frontend
npm i @angular/elements
app.component.html
to be something simple, like hello world:<p>Hello from micro frontend component!</p>
app.module.ts
to import Injector
from @angular/core
, make the bootstrap array empty, and add the custom element app-micro-frontend
.
Name it whatever you like, although the Angular convention starts with app
. Your app.module.ts
should look like this:import { Injector, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { createCustomElement } from '@angular/elements';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: []
})
export class AppModule {
constructor(private injector: Injector) { }
ngDoBootstrap() {
const element = createCustomElement(AppComponent, {
injector: this.injector
});
customElements.define('app-micro-frontend', element);
}
}
app-root
with your new element app-micro-frontend
in your index.html
file in order to reflect your new element. It should look like this:<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>MicroFrontend</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-micro-frontend></app-micro-frontend>
</body>
</html>
Optional
ng serve
, which should work with the newly created element.main.ts
file to add the noop
argument like so:import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule, { ngZone: 'noop' })
.catch(err => console.error(err));
package.json
file under scripts. Unfortunately, Angular does not have an option to make sure there is only one JS file when building.ng build -c production
will build the production version of your app. -prod
was depreciated and no longer works.--output-hashing=none
is also necessary so the separate JS files do not have a long hash added to their name.cat ./dist/ANGULAR_PROJECT_NAME/*.js > ./dist/ANGULAR_PROJECT_NAME/app-micro-frontend.js
– This will allow you to concatenate all the production JS files into one large JS file called app-micro-frontend.js
.
However,
cat
may not be available on your machine by default. Windows has the type
command and PowerShell has get-content
, etc. To keep this working across
all devices, I decided to use bash with
cat
. I don’t want to create a whole new script when a command should suffice here."scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"bundle": "ng build -c production --output-hashing=none && bash -c \"cat ./dist/micro-frontend/*.js > ./dist/micro-frontend/app-micro.js\""
},
Run npm run bundle
to bundle your application.
Go to the ./dist/micro-frontend
folder and edit the index.html
file. Replace the three script import tag files: runtime.js
, pollyfills.js
,
and main.js
with app-micro.js
:
<!doctype html>
<html lang="en" data-critters-container>
<head>
<meta charset="utf-8">
<title>MicroFrontend</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css"></head>
<body>
<app-micro-frontend></app-micro-frontend>
<script src="app-micro-frontend.js" type="module"></script>
</body>
</html>
live-server
extension. You may not need to do this if you use another server for testing.<base href="/dist/micro-frontend/">
./dist/micro-frontend/index.html
file and click “Open with Live Server.” You should now see the final web component:Hello from micro frontend component!
See my Angular Elements Repository for a working example.
Separating your Angular files into micro frontends can be a great way to simplify your life. It may not be for everyone, as sharing packages, standard lazy-loading techniques, and Git techniques could be an easier approach. There are also some challenges
when it comes to sharing state across these apps—you must track window
events, etc.
Nevertheless, using Angular elements can make this extremely easy, and could allow one team member to write a component in React, while another uses Vue. It is important to know about, and definitely useful for many large project teams.
Jonathan Gamble has been an avid web programmer for more than 20 years. He has been building web applications as a hobby since he was 16 years old, and he received a post-bachelor’s in Computer Science from Oregon State. His real passions are language learning and playing rock piano, but he never gets away from coding. Read more from him at https://code.build/.