Search code examples
angularmicro-frontendwebpack-module-federation

How to pass data into a remotely loaded component


I am trying to establish a microfronted architecture using Angular as the base framework and setting up hosts and remotes via Webpack Module Federation.

I have a few scenarios up and running, mostly working with the guidelines provided in this series by angulararchtitects.io I managed to build a shell application that fetches remote components at runtime and plugs them into some sort of dashboard page, which is amazing :)

However, some components need an initial parameter from the host/shell application, i.e. some ID for which the content should be loaded. I had hoped to pass this in via an input parameter. The blog suggests that this is possible - the case "set property" in the highlighted text seems to be exactly my scenario.

enter image description here

However, when I try to use code like the second commented one, I can't compile as compInstance is of type "unknown". This also makes sense because I am loading the component at runtime and the code can't possibly know anything about it beforehand, right? But how then would it be possible to interact with the remote component as the text suggests? Or is there another way to achieve this? Maybe to "blindly" set data on the remote component, leading to runtime errors if I used wrong inputs etc. (something like compInstance.set['initialId'])?


Solution

  • The comments and link by Jake Smith pointed me in the right direction, I think. I managed to make this work in the following way:

    1. In the shell repo I already had a folder called "remotes" holding the .d.ts declarations file to make some external modules known. In this folder I now added a file to hold the "contract" the shell can expect from a specific remote, e.g. my-external-feature.ts and it looks like this:

    export interface HasInputProperty {
        myInput: string;
    }
    
    //  Checks to assert the required interaction points
    export function checkForInputProperty(obj: unknown): obj is HasInputProperty {
        return hasProperty(obj as Object, 'myInput');
    }
    
    // Helper function (is handy if (in the real world) multiple checks are in order)
    function hasProperty(obj: Object, property: string) {
        return (typeof obj === 'object' && obj !== null) && property in obj;
    }

    1. In the shell the component that puts together the bits and pieces uses a check/cast against this contract. It can react to non-conform results, but in the positive case it can then use the remote component as intended.

     const imported = await import('my-external-feature/Component');
     const ref = this.someViewContainer.createComponent(imported.Component);
    
     // In order to interact with the remote component we first verify that it has the needed properties    
     const component = ref.instance;
        if (checkForInputProperty(component)) {
          component.myInput = 'Here we can add the desired input';
        } else {
          alert('Noooo - did the other team change the contract without telling us?');
        }    

    This works fine now and I quite like the idea to define a minimal contract and handle differing results. From some quick test it also seems like all the Angular magic for change detection upon changing input variables seems to work just fine, so "valueChanges" from outside are handled just like always.


    However, in another place I recently used the way suggested by ghosh, namely to trigger some CustomEvent via DOM (in the shell) and setup a listener inside the component (Remotely integrated). There it was more suitable because it was used in a service, no component involvded. It has a similar effect, but I needed to retrieve some inital value from the session storage, as the service was only instantiated after the first (initializing) event was sent, so only subsequent changes could be listened to.

    So if an initial param to a plugin-component is needed, I think the approach above is a valuable alternative. Nice to have some tools in the tool-box :)

    Thanks for all the valuable inputs!