{{announcement.body}}
{{announcement.title}}

Micro Frontends - A Simple Demo

DZone 's Guide to

Micro Frontends - A Simple Demo

In this article, take a look at micro frontends and see a simple demo.

· Web Dev Zone ·
Free Resource

There are many articles in DZone and videos on the internet on Micro Frontends. So I'll skip the details and jump to the article. This article is about demonstrating the development of Micro Frontends using vanilla web components. I consider two Micro Frontends that are deployed independently of each other to two sub-domains and using different code repositories. These frontends are then assembled as one SPA. I'll then discuss the communication between the frontends and then present a working online demo of this SPA. 

The SPA - The Desired Result

The screenshot of the SPA for this demo is:



where two micro frontends, productsApp and cartApp, are assembled as one SPA. The user can select one or more products, add them to the cart and then view their selections in the cart. The two frontend apps are deployed independently of each other with their own sub-domains. The code for the two apps are also in separate repositories.

Micro Frontend #1 - Products App

The  productsApp.html  uses a web component, ProductsComp with a custom element like:

 <products-comp id="productsApp" ></products-comp> 

The source for  products-comp.js  is:

JavaScript
 




x


1
class ProductsComp extends HTMLElement {
2
    constructor() {
3
        super();
4
 
          
5
        this.addEventListener('change', e => {
6
            if (e.target.type === "checkbox") {
7
                let atleastOneChecked = false;
8
                for (let el of this.getElementsByTagName('input')) {
9
                    if (el.checked) {
10
                        atleastOneChecked = true;
11
                        break;
12
                    }
13
                }
14
                document.getElementById("productsCompBtn").disabled = !atleastOneChecked;
15
            }
16
        });
17
 
          
18
        this.addEventListener('click', e => {
19
            if (e.target.type === "submit") {
20
                let selectedFruits = new Array();
21
                for (let el of this.getElementsByTagName('input')) {
22
                    if (el.checked) {
23
                        selectedFruits.push(el.getAttribute("value"));
24
                    }
25
                };
26
                if (selectedFruits.length > 0) {
27
                    this.dispatchEvent(
28
                        new CustomEvent('selectedFruits', {
29
                            detail: selectedFruits
30
                        })
31
                    );
32
                }
33
            }
34
        });
35
 
          
36
    }
37
 
          
38
    connectedCallback() {
39
        this.innerHTML += this.createProductsApp();
40
    }
41
 
          
42
    createProductsApp() {
43
 
          
44
        return `<div>
45
        <div class="row row-cols-3">
46
            <div class="col-sm"><img data-fr-src="apple.jpg" height="150" width="150"><br />
47
            <span style="color: black;">Apple $1.00/lb</span> 
48
            <input type="checkbox" name="fruits" value="apple">
49
            </div>
50
            <div class="col-sm"><img src="kiwi.jpg" height="150" width="150"><br />
51
            <span style="color: black;">Kiwi $2.00/lb</span> <input type="checkbox" name="fruits" value="kiwi">
52
            </div>
53
            <div class="col-sm"><img src="orange.jpg" height="150" width="150"><br />
54
            <span style="color: black;">Orange $1.00/lb</span> <input type="checkbox" name="fruits" value="orange">
55
            </div>
56
            </div>
57
        </div>
58
        <div class="row row row-cols-3"><div class="col-sm">&nbsp;</div></div>
59
        <div class="row row-cols-3"><div class="col-sm d-flex justify-content-center">
60
        <p><button id="productsCompBtn" class="btn btn-primary" disabled>Add to Cart</button></p></div></div>`;
61
    }
62
}
63
window.customElements.define('products-comp', ProductsComp);


where, for brevity of discussion, I am not using Shadow DOM feature of web components. All the code for this frontend is stored in its own repository.

and deployed to its own sub-domain:
https://mapteb.github.io/micro-frontends-using-webcomponents/productsApp.html 

Micro Frontend #2 - Cart App

The   cartApp.html  uses a web component, CartComp with a custom element like:

 <cart-comp id="cartApp" ></cart-comp> 

The source for   cart-comp.js   is:

JavaScript
 




x


 
1
class CartComp extends HTMLElement {
2
3
    constructor() {
4
        super();
5
        
6
        this.addEventListener('cartLoaded', e => {
7
            this.innerHTML = this.getCartDoc(e.detail);
8
        });
9
    }
10
11
    getCartDoc(detail) {
12
13
        return `<ol class="list-group">${this.getLineItems(detail)}</ol>`;
14
    }
15
16
    getLineItems(detail) {
17
        let lineItems = '';
18
        for (let s of detail) {
19
            lineItems += `<li class="list-group-item"><img data-fr-src="${s}.jpg" alt="${s}" height="20" width="20">${s}</li>`;
20
        }
21
        return lineItems;
22
    }
23
}
24
window.customElements.define('cart-comp', CartComp);


All the code for this frontend is stored in its own repository.

and deployed to its own sub-domain:
https://ns-code.github.io/micro-frontends-cartapp/cartApp.html

Frontends Assembly - SPA

The two frontends are assembled as one SPA in this index.html:

HTML
 




x


1
<!doctype html>
2
<html lang="en">
3
 
          
4
<head>
5
  <meta charset="utf-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <meta name="description" content="A layout example that shows off a responsive product landing page.">
8
  <title>SPA with two micro-frontends</title>
9
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
10
    integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
11
</head>
12
<style>
13
  #dialog {
14
    width: 300px;
15
  }
16
 
          
17
  .right {
18
    float: right
19
  }
20
</style>
21
 
          
22
<body>
23
 
          
24
  <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
25
    <a class="navbar-brand" href="#">Welcome to Micro-Frontends Demo</a>
26
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse"
27
      aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
28
      <span class="navbar-toggler-icon"></span>
29
    </button>
30
    <div class="collapse navbar-collapse" id="navbarCollapse">
31
      <ul class="navbar-nav mr-auto">
32
        <li class="nav-item">
33
          <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">&nbsp;</a>
34
        </li>
35
      </ul>
36
      <button class="btn btn-primary" data-toggle="modal" data-target="#staticBackdrop" onclick="">Cart (<span
37
          id="itemsCount"></span>)</button>
38
    </div>
39
  </nav>
40
 
          
41
  <!-- Modal -->
42
  <div class="modal fade" id="staticBackdrop" data-backdrop="static" tabindex="-1" role="dialog"
43
    aria-labelledby="staticBackdropLabel" aria-hidden="true">
44
    <div class="modal-dialog" role="document">
45
      <div class="modal-content">
46
        <div class="modal-header">
47
          <h5 class="modal-title" id="staticBackdropLabel">Cart Items</h5>
48
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
49
            <span aria-hidden="true">&times;</span>
50
          </button>
51
        </div>
52
        <div class="modal-body">
53
          <cart-comp id="cartapp"></cart-comp>
54
        </div>
55
        <div class="modal-footer">
56
          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
57
        </div>
58
      </div>
59
    </div>
60
  </div>
61
 
          
62
  <main role="main" class="container">
63
    <div class="jumbotron">
64
      <div class="container">
65
        <div class="row">
66
          <div class="col-sm">
67
            <p>Please use Chrome or Firefox to view this page.</p>
68
            <h3>Select one or more Fruits</h3>
69
          </div>
70
        </div>
71
      </div>
72
      <div class="container">
73
        <div class="row">
74
          <div class="col-sm">
75
            <p>
76
              <products-comp id="productsApp"></products-comp>
77
            </p>
78
 
          
79
          </div>
80
        </div>
81
      </div>
82
    </div>
83
  </main>
84
  <section>
85
    <div>
86
      <section id="productsAppView">
87
        <span>
88
        </span>
89
      </section>
90
    </div>
91
  </section>
92
 
          
93
  <script>
94
    document.getElementById("productsApp").addEventListener('selectedFruits', e => {
95
      if (e.detail != null && e.detail.length > 0) {
96
        document.getElementById("cartapp").dispatchEvent(
97
          new CustomEvent('cartLoaded', {
98
            detail: e.detail
99
          })
100
        );
101
        let elem = document.getElementById("itemsCount");
102
        let count = elem.innerText;
103
        elem.innerText = '' + e.detail.length;
104
      }
105
    });
106
  </script>
107
  <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
108
    integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
109
    crossorigin="anonymous"></script>
110
  <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
111
    integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
112
    crossorigin="anonymous"></script>
113
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
114
    integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
115
    crossorigin="anonymous"></script>
116
  <!-- web components -->
117
  <script src="products-comp.js"></script>
118
  <script src="https://ns-code.github.io/micro-frontends-cartapp/cart-comp.js">
119
  </script>
120
</body>
121
</html>


where custom elements from the two frontends are used. The JavaScript files for the two frontends are also imported. When the user selects one or more fruits and adds them to the cart, the ProductsComp dispatches an event called selectedFruits. The JavaScript listener in index.html receives it and dispatches the data to another custom event called cartLoaded. The Cart App listener  then receives this event data and displays it as items in the  cart.

Here is the online demo for this SPA.

Conclusions

Modern browsers supported vanilla web components and event handling mechanisms are shown to provide a simple way to get started with micro frontends. The online demo hints at the many touted benefits of using micro frontends - multiple teams working in parallel and independently supporting complex SPAs.

Related Works

Interested readers can checkout the following:
1. https://micro-frontends.org/ (online demo)
2. https://github.com/kito99/micro-frontends-demo
3. https://luigi-project.io/
4. https://piral.io/
5. https://www.mashroom-server.com/ 

Topics:
micro frontends, tutorial, web components, web dev

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}