Orchestrating communication between microfrontends and the wrapper application.
Tags: microfrontend, webdev, javascript, angular
Published: 2023-03-19 14:57:53

In this article, we will discuss how we developed a custom solution for communication between microfrontends and the wrapper application. Written under the assumption that readers are aware of the basics of microfrontend architecture. A microfrontend makes your app more manageable and scalable, along with the freedom to work on any technologies you want for specific frontend functionality.

 During development, one major issue we faced was how to make communication more seamless between microfrontends and the wrapper app. The main objective we had was to invoke a specific method from the frontend element whenever an outer menu from the wrapper app is accessed. Since a microfrontend is a standalone app, it does not have a relationship with the wrapper app and simply acts as a container

Most solutions we found online are very complex or involves an external third party application.Finally our architect came up with custom solution which is very simple and easy to implement.

The solution we developed involves a message bus and a custom navigation manager..(Both files are added below)

Message bus
               Message bus is just a js file containing 4 functions and global variable  topicSubscriptions(which is an empty object).The messagebus follows singleton pattern to control the instance creation.

Subscribe:
The function first checks if the topic already has a list of subscriptions by looking up the "topicName" key in  "topicSubscriptions". If the key exists, the function pushes the new subscription function ("func") to the existing list. If the key doesn't exist, the function creates a new key-value pair in "topicSubscriptions" with "topicName" as the key and an array containing the new subscription function as the value.

In other words, this function is part of a messaging system that allows different parts of a program to subscribe to certain topics and receive messages when those topics are updated. The "subscribe" function is responsible for adding new subscriptions to the system.

Unsubscrbe
The function first checks if the topic has a list of subscriptions by looking up the "topicName" key in the "topicSubscriptions" object. If the key exists, the function retrieves the list of subscriptions for that topic and assigns it to a variable called "subscriptions". If the "subscriptions" variable has a truthy value (meaning it's not null or undefined), the function removes the "func" subscription from the list of subscriptions using the Array.filter() method.

Publish
The function first checks if there are any subscribers for the topic by looking up the "topicName" key in the "topicSubscriptions" object. If the key exists, the function iterate over each subscription function in the "subscriptions" array and call it .

HasSubscriptions
This function can be used to check whether a topic has any subscribers before publishing a message to that topic.

Navigation Manager
Creates an instance object messagebus class.
Which has an OnNavigation function that checks whether there are any subscribers for the "OUTER_NAVIGATION_REQUESTED" topic using the "hasSubscriptions" method of a "bus" object. If there are no subscribers, the method simply redirects the user to the new page using the "window.location.href" property.
If there are subscribers for the "OUTER_NAVIGATION_REQUESTED" topic, the method publishes a message to the "bus" messaging system using the "publish" method of the "bus" object. 


Microfrontend has a reference to messagebus file  and on the elment initialisation it subscribes to OUTER_NAVIGATION_REQUESTED topic along with function name that has to be called when the user requests for outer nvaigation.

Wrapper application has both references to messagebus and navigation manager files.So on clicking outer menus on the wrapper application the navigation manager invokes Onnavigation method which checks for  OUTER_NAVIGATION_REQUESTED topic and invokes the functions subscribed. 


```
var messageBus = function () {

    //Wrapping the actual bus here to control the instance creation
    var bus = function () {
        var
            topicSubscriptions = {},

            subscribe = function (topicName, func) {

                if (topicSubscriptions[topicName]) {
                    topicSubscriptions[topicName].push(func);
                } else {
                    topicSubscriptions[topicName] = [func];
                }

            },

            unsubscribe = function (topicName, func) {
                var subscriptions = topicSubscriptions[topicName];
                if (subscriptions) {
                    topicSubscriptions[topicName] =
                        topicSubscriptions[topicName]
                            .filter((subscriber) => subscriber !== func);
                }
            },

            publish = function (topicName, message) {
                var subscriptions = topicSubscriptions[topicName];
                if (subscriptions) {
                    subscriptions.forEach((func) => {
                        func(message);
                    });
                } else {
                    console.log("No subscriptions for topic: "+ topicName);
                }
            },

            hasSubscriptions = function (topicName) {
                return topicSubscriptions[topicName] ? true : false;
            };

        return {

            subscribe: subscribe,
            unsubscribe: unsubscribe,
            publish: publish,
            hasSubscriptions: hasSubscriptions

        };
    };

    return {
        //Returns singleton instance
        getInstance: function () {

            if (!window.messageBusInstance) {
                window.messageBusInstance = bus();                
            }

            return window.messageBusInstance;
        }
    }
}();
```
messagebus.js


```
var navigationManager = function () {

    var
        bus = messageBus.getInstance(),

        registerEvents = function () {

            $(".outer-menu").on('click', function(e) {
                e.preventDefault();
                events.onNavigation(this);

            });
           
        },

        events = {
            onNavigation: (self) => {
               
                var $this = $(self);
                var navPath = $this.attr("href");

                if (!bus.hasSubscriptions("OUTER_NAVIGATION_REQUESTED")) {

                    window.location.href = navPath;
                } else {

                    bus.publish("OUTER_NAVIGATION_REQUESTED", { url: navPa      th });
                }
            }
        },

        init = function () {
            registerEvents();
        };

    return {
        init: init
    };

}();

$(document).ready(function () {

    navigationManager.init();
});
```
navigationmanager.js