LEHMAMIC
LEHMAMIC
BlogAbout
ARCHITECTURE
UI COMPOSITION
SELF-CONTAINED SYSTEMS
MICRO-FRONTEND
STENCILJS
WEBCOMPONENTS
ANGULAR

A step closer towards micro-frontend

Michael Lehmann
Michael Lehmann13 June 20226 min read

In my previous post, I described our way to build a library to share layout, styles and components in the user interface of our self-contained systems architecture.

This helped us a lot, because it reduced the coupling of the shared code to the applications in our system. But we started to struggle with it again after an inconsistent navigation in the header due to an update in the shared header component. Let's be serious, this is bad and should not happen! It is ok, when some little css styling is not up do date in application. Most likely nobody will recognize that. But the navigation must be consistent in a self-contained system. It is bad, when the navigation to an application suddenly disappears when the user navigates through the system. Even worse, when there is a dead link in the navigation.

Dynamically rendered Layout

We discussed two options to solve that problem:

  • Extracting the header and footer into its own shared library. All the infrastructure for this approach already exist. It would be easy to build and would make it easier to automatically integrate and update it in all applications. But it still has a dependency to certain npm package versions which can turn an update into a bigger exercise.
  • Deploy the header and footer separately and integrate it into the applications during runtime. This is clearly a step towards a micro-frontend architecture. We initially decided against micro-frontends because it would increase the complexity and our project is not large enough that it would be worth to use that pattern. But for the current problem it seemed to be a good fit.

We decided, obviously, to deploy the header and footer separately and integrate it at runtime into our applications. That we would implementing it by using WebComponents, was also clear from the beginning. Still open was the Framework we want to use. We are using Angular. Angular Elements could be a fit, but has some drawbacks.

Our goal was to create a single js file, deploy it to an Azure Blob Storage and dynamically include it via <script> tag. Lets have a look at the options we evaluated:

  • Angular Elements, obviously. It is possible to create a single file with ngx-build-plus which is was we would like to have. But Angular Elements is building a complete Angular application into the WebComponent, hence it will be a rather large bundle.
  • StencilJS, developed by the Ionic team to make their UI components independent of the UI framework.
  • LitElement, just another lightweight framework to write WebComponents.

We decided to use StencilJS. We think it is the better option than Angular Elements, even though it is actually designed for compile time integration. We want to have a fast application and the bundle size of Angular Element is ways larger than any output from StencilJS.

Create and Stencil project

Let's have a look at our setup with StencilJS and the integration in our Angular applications. We are using NX for our UI workspaces. Lets create an NX workspace with a StencilJS project.

First we create the NX workspace with typescript presets:

npx create-nx-workspace my-workspace --preset=ts

Afterwards we are going to create a Stencil project in our freshly created NX workspace by using the NX plugin from Nnext:

npm install @nxext/stencil --save-dev
nx g @nxext/stencil:library my-lib

The Nnext plugin provides a generator which allows to create our first component:

nx g @nxext/stencil:component my-header --project my-lib

This outputs a component skeleton like this:

@Component({
  tag: 'my-header',
  styleUrl: 'my-header.scss',
  shadow: true,
})
export class MyHeader {
  @Prop() first: string;
  @Prop() middle: string;
  @Prop() last: string;

  private getText(): string {
    return (this.first || '') + (this.middle ? ` ${this.middle}` : '') + (this.last ? ` ${this.last}` : '');
  }

  render() {
    return <div>Hello, World! I'm {this.getText()}</div>;
  }
}

I will not going to write a StencilJS tutorial at this place. But one thing I need to mention. Stencil provides several output targets. We actually want to have a single file bundle which would be the dist-custom-elements-bundle out target. Unfortunately this has been deprecated. We use its successor dist-custom-elements. It does basically the same thing, but produces a js file per component. Which is the better way because it is better to load several smaller bundles than a large one. It will also be better for leveraging http2. We would prefer a single file because it means less file handling in our frontend's, but we can work with that.

outputTargets: [
  {
    type: 'dist-custom-elements',
  },
];

Finally, we are deploying these output files in an Azure Blob Storage. But any static hosting would do the job.

It sound that easy, but nobody of our team head a real experience in StencilJS and especially lazy load them in an Angular application. On problem worth to mention is the handling of assets. If the component uses assets which gets deployed, you need to make sure to load them with an absolute path. Relative paths get resolved to the URL where the application is deployed. We solved the problem by compiling everything (e.g. svg icons and json translation files) into the bundle. This way, we don't need to load them during the runtime.

Consuming the WebComponents dynamically during run time

After creating and deploying our WebComponents, they need to be integrated into the frontend of our applications. A simple script element loading js bundle will do the job. Than we just can use the custom element:

<script type="module" src='https://cdn.jsdelivr.net/npm/my-name@0.0.1/dist/myname.js'></script>
<my-header></my-header>

We found a nice library, ANGULAR EXTENSIONS ELEMENTS, which makes it even more easy to lazy load elements in Angular. It also supports displaying special components for the loading and error cases.

<my-header *axLazyElement="headerUrl; errorTemplate: errorHeader; loadingTemplate: loading; module: true"></my-header>

One thing which needs to be done to make this work, is defining the custom elements schema on the corresponding Angular module.

@NgModule({
  declarations: [...],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  imports: [ LazyElementsModule],
})

Summary

Not all of our problems got solved by introducing a shared Angular library. We came to the conclusion that a independently deployed and lazy loaded header and footer would be the best solution for us. We decided to use WebComponents for that job. Finally we implemented it with StencilJS and integrated it with the Angular Extension Elements library.

Implementing header and footer as micro-frontend was not very smooth. We had a steep learning curve and spent a lot more time than expected. But we are very happy with the result and I, personally, would do that definitely again.

Credits

Photo by Erik-Jan Leusink on Unsplash