javascriptangularrxjsmicro-frontendwebpack-module-federation

Conflicts with RxJS switchMap and Appcues Script in a Module Federation Microfrontend Architecture


I'm encountering a persistent issue when integrating the Appcues script into our Angular application, which utilizes Module Federation for implementing microfrontends. The problem arises specifically when the Appcues script is loaded, affecting the behavior of RxJS switchMap operators used throughout our application. Appcues is a third party system to track user loggins and to display banners in the web where it is integrated.

Problem Description:

In our setup, we dynamically inject the Appcues script into one of our microfrontends. The script is added to the DOM using the following JavaScript code:

const script = document.createElement('script');
script.async = true;
script.id = 'appcues';
script.src = 'https://fast.appcues.com/Id_Value.js'; // Example src
document.body.appendChild(script);

This microfrontend architecture is managed with Module Federation, allowing us to operate multiple independent builds as part of a cohesive platform.

Example of SwithMap Usage:

public addHandler(address: ValidatedAddress): void {
        this.sub.add(
            this._AddressService
                .getAddressById(address)
                .pipe(
                    switchMap((existingAddress) => {
                        if (existingAddress) {
                            return of(existingAddress);
                        }
                        return this._AddressService.addValidatedAddress(address);
                    })
                )
                .subscribe({
                    next: (newAddress: ValidatedAddress) => {                       
                        this.saveAddress(newAddress);
                    },
                })
        );
    }

Issue Observed:

The issue does not occur immediately after loading the script. Instead, it appears after navigating away from the initial page (where the script is loaded) and then interacting with components that rely on RxJS's switchMap. The error thrown is:

TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

This error suggests a collision or interference in the handling of observables, possibly caused by how Appcues manipulates the global environment or interacts with the JavaScript event loop, impacting RxJS operations.

Steps to Reproduce:

Load the Appcues script in one of the microfrontends. Navigate to another microfrontend and perform an action that uses switchMap (from rxjs) The error begins to appear, if I return to the microfrontend where the script was loaded the error is not present.

What I've Tried:

Ensuring that the script is loaded asynchronously and checked for errors. Delaying the loading of the Appcues script to allow other initializations to complete. Using Angular's NgZone to isolate the script loading from Angular's change detection mechanisms. Suspicions:

It seems like the Appcues script might be altering some global variables or aspects of the JavaScript runtime that RxJS depends on, particularly around how asynchronous operations are handled.

Questions:

Has anyone experienced similar issues with Appcues or other third-party scripts interfering with RxJS? Are there known compatibility issues with Appcues and RxJS when used in a microfrontend architecture managed by Module Federation? What strategies might be effective in isolating the third-party script's effects from the rest of the application? Any insights or suggestions on how to address these issues without removing Appcues would be greatly appreciated. Thank you!


Solution

  • You can try adding this query at the top of the head in index.html:

    <script>
        if (typeof window.Symbol === 'function') {
            if (!window.Symbol.observable) {
                window.Symbol.observable = window.Symbol('observable');
            }
        }
    </script>

    The explanation for why this works:

    Appcues might be modifying how observables are detected in the global environment. Specifically, it might interfere with Symbol.observable, which RxJS uses to recognize observables. With that snippet you are manually defining Symbol.observable before anything else in the app.