Angular, React, Vue.Js and Co.

Peacefully United thanks to Micro Apps and Web Components



Source Code

In my article Micro Apps With Web Components Using Angular Elements I'm showing how to leverage Angular Elements and Web Components to implement a modern Micro Apps architecture. It shows a shell that loads individual Micro Apps that can be developed as well as deployed separately. Those Micro Apps are provided as Web Components and loaded dynamically at runtime.

In this article I'm going one step further: I'm using different technologies for different micro apps.

Of course, you don't do something like this just for fun. It's rather necessary if your application evolves for a long time like one decade or more and if you have several UI teams. In this case, each team can go with the "best" technology for their part and you have not to stick with your original technology decision for ten years or longer.

Case Study

To show how to mix and match technologies, I'm using an extend version of the case study from my last post:

Using several technologies in one app

Here, you see a shell application with an Angular-based Micro App containing a Vue-based and a VanillaJS-based widget.

You can also mix and match technologies at macro-level for providing different micro apps:

Mixing technologies at macro level

Besides this, each Micro App can also run standalone:

Micro Apps in standalone mode

This is important because it allows for separate development, testing and deployment or to put it in another way: You are minimizing dependencies between different UI teams.

Wrapping a Micro App in a Web Component

For wrapping the Angular parts of my example in Web Components, I'm using Angular Elements. Further information about this can be found in my article here.

For the Vue.JS part, I'm directly using the native Custom Elements API. As an alternative, you could also use the build-in option for providing Web Components which is available since version 3. However, while it seems to be a good fit for widgets, I felt I needed more flexibility for wrapping a whole micro app which also uses the router or other libraries.

The next listing shows how to use the Custom Elements API for wrapping the Micro App:

import Vue from 'vue' import Booking from '../components/Booking.vue' import initRouter from '../initRouter.js'; import store from '../SimpleStore.js'; export default class FlightBooking extends HTMLElement { get appState() { return this._appState; } set appState(value) { this._appState = value; this.vue.$data.appState = value; } static get observedAttributes() { return ['app-state']; } attributeChangedCallback(name, oldValue, newValue) { this.appState = JSON.parse(newValue); } constructor() { super(); this.attachShadow({ mode: 'open' }); this.render(); } render() { const cssBase = require('!to-string-loader!css-loader!../assets/css/bootstrap.min.css'); const cssTheme = require('!to-string-loader!css-loader!../assets/css/paper-dashboard.css') this.shadowRoot.innerHTML = ` <style>${cssBase}</style> <style>${cssTheme}</style> <div id="component"></div> `; const router = initRouter(); const handleMessageEvent = (msg) => { this.dispatchEvent(new CustomEvent('message', { detail: msg })); } this.vue = new Vue({ router, data: { store, appState: this.appState }, render(r) { return r(Booking, { props: { appState: this.appState }, on: { message: handleMessageEvent } }); } }); const comp = this.shadowRoot.getElementById('component'); this.vue.$mount(comp); } }

The Web Component is just a subclass of HtmlElement. For the communication with the shell, all my Micro Apps come with an appState property and a message event. For instance, the appState contains the passenger and flight in question and the message event informs the system when a flight has been booked.

I'm also syncing the app-state attribute with the appState property. Every time the appState changes, I'm passing it to the current Vue object.

By means of attachShadow I'm using Shadow DOM which isolates the component's layout from the rest of the application. Simply spoken, this prevents a global CSS from destroying the component's layout.

The render method displays the component by leveraging Vue. To load some CSS instructions just for the component, it creates an own style tag. The CSS itself is loaded using webpack's to-string-loader and css-loader. They have to be npm-installed separately.

The Vue object gets a configured router, a data object with values for the component (e. g. the appState) and a render method.

The latter one is a bit confusing. If you would rewrite it using a html element with data binding expressions it would look like this:

<booking :app-state="appState" @message="handleMessageEvent"></booking>

For using such a template, we'd need the Vue template compiler at runtime.

However, to get the best performance, it's a best practice to precompile Vue-templates. Hence, we don't need not to put the Vue compiler into our bundles.

While the Vue CLI automatically precompiles all template in vue files we have to provide this render function in our wrapping Web Component instead of the template to get rid of the compiler at runtime.

Registering Web Components

To register this web component alongside another one wrapping the basked shown above, we just call customElements.define in the application's entry point which is normally called main.js:

import Vue from 'vue' import FlightBookingCE from './custom-elements/FlightBookingCE.js' import FlightBasketCE from './custom-elements/FlightBasketCE.js' import VueRouter from 'vue-router' Vue.use(VueRouter); customElements.define('flight-booking', FlightBookingCE); customElements.define('flight-basket', FlightBasketCE);

We can also use this entry point to register plugins like the router.

To get the bundles, just call the VUE.js CLI: vue-cli-service build.

This command gives us two bundles we need to load together. To load them, we can dynamically create script tags and tags for the web components as shown here.

Summary

Micro Apps and Web Components allow for mixing different technologies. This can be important for applications developed over a long time and/or by different UI teams. You can use the "best" technology for each part of the application and you don't need to stick with your technology decision forever.

[1] https://www.angulararchitects.io/post/2018/05/04/microservice-clients-with-web-components-using-angular-elements-dreams-of-the-near-future.aspx