Search code examples
javascriptangularweb-componentcustom-element

Is it possible to use different angular elements built with different versions of Angular


I would like to know if it is possible to use different angular elements (custom elements) built with different versions of Angular. I have heard that zone.js was polluting the global scope.

Thanks for your answer.


Solution

  • Yes, you have heard it correct. We cannot use multiple angular elements if each angular element created from a specific version is trying to load zonejs.

    Having said that it is 100% possible to have multiple angular elements of different versions on a single page. All we need to take care of is loading zone js only once and sharing it across all the web-components(Angular Elements).

    While bootstrapping multiple elements we can add the logic of not loading/patching zonejs if already loaded as below:

    Remove zonejs polyfill from polyfill.ts for all Angular Elements

    Create a file in main.ts level. Let's say bootstraper.ts :

    export class Bootstrapper {
      constructor(
        private bootstrapFunction: (bootstrapper: Bootstrapper) => void
      ) {}
    
      /**
       * Before bootstrapping the app, we need to determine if Zone has already
       * been loaded and if not, load it before bootstrapping the application.
       */
      startup(): void {
        console.log('NG: Bootstrapping app...');
    
        if (!window['Zone']) {
          // we need to load zone.js
          console.group('Zone: has not been loaded. Loading now...');
          // This is the minified version of zone
          const zoneFile = `/some/shared/location/zone.min.js`;
    
          const filesToLoad = [zoneFile];
    
          const req = window['require'];
          if (typeof req !== 'undefined') {
            req(filesToLoad, () => {
              this.bootstrapFunction(this);
              console.groupEnd();
            });
          } else {
            let sequence: Promise<any> = Promise.resolve();
            filesToLoad.forEach((file: string) => {
              sequence = sequence.then(() => {
                return this.loadScript(file);
              });
            });
    
            sequence.then(
              () => {
                this.bootstrapFunction(this);
                console.groupEnd();
              },
              (error: any) => {
                console.error('Error occurred loading necessary files', error);
                console.groupEnd();
              }
            );
          }
        } else {
          // zone already exists
          this.bootstrapFunction(this);
        }
      }
    
      /**
       * Loads a script and adds it to the head.
       * @param fileName
       * @returns a Promise that will resolve with the file name
       */
      loadScript(fileName: string): Promise<any> {
        return new Promise(resolve => {
          console.log('Zone: Loading file... ' + fileName);
          const script = document.createElement('script');
          script.src = fileName;
          script.type = 'text/javascript';
          script.onload = () => {
            console.log('\tDone');
            resolve(fileName);
          };
          document.getElementsByTagName('head')[0].appendChild(script);
        });
      }
    }
    

    And in main.ts we can change the bootstrap logic to the below one :

    import { enableProdMode } from '@angular/core';
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    
    import { AppModule } from './app/app.module';
    import { Bootstrapper } from './bootstraper';
    const bootstrapApp = function(): void {
      platformBrowserDynamic()
        .bootstrapModule(AppModule)
        .then(() => {})
        .catch(err => console.error(err));
    };
    
    const bootstrapper = new Bootstrapper(bootstrapApp);
    bootstrapper.startup();
    

    This way we can definitely create multiple Angular Elements (Web Components) and use in a SPA.

    Thanks