Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten in custom element
Asked Answered
K

2

12

I created a small app using angular custom elements feature (element.js), imported that element.js file into another angular(parent) app in index.html, in development server (ng serve) element feature works fine, but in production mode (ng build --prod) getting this error in element.js file.

@angular/core": "~8.1.3", @angular/elements": "^8.1.3"

element (angular custom element code)
polyfills.ts

import 'zone.js/dist/zone';  // Included with Angular CLI.
import "@webcomponents/custom-elements/src/native-shim";
import "@webcomponents/custom-elements/custom-elements.min";

app.module.ts
export class AppModule {
  constructor(private injector: Injector) { }

  ngDoBootstrap() {

    const el = createCustomElement(NotificationElementComponent, {
      injector: this.injector
    });
    // using built in the browser to create your own custome element name
    customElements.define('test-card', el);
  }
}


angular (parent app)
index.html
<!doctype html>
<html lang="en">
<body>
    <app-root></app-root>
    <script src="./assets/elements.js"></script>
</body>
</html>

polyfills.ts

import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';  // Included with Angular CLI.

(window as any).global = window;

app.component.html
 <test-card [data]="{id:"foo"}"></test-card>

Error Zone.js has detected that ZoneAwarePromise (window|global).Promise has been overwritten. Most likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.).

Kyanize answered 28/8, 2019 at 15:41 Comment(2)
did you have any luck? I have the same problem in prod but not dev and I have no idea what to doLodhia
I'm getting the same error as well with a new Angular app. Anyone resolve this?Seattle
R
1

To save yourself a load of headaches it's advisable to remove Zone when using Angular Elements and handle the change detection yourself.

platformBrowserDynamic()
  .bootstrapModule(MainModule, { ngZone: 'noop'})
  .catch(err => console.error(err));

Then make sure you remove it from your PolyFills.

Reddick answered 5/9, 2019 at 20:45 Comment(1)
Error is gone but I don't see my Chart and Material-table on browser.Monique
M
0

We cannot load zonejs multiple times. The reason is once zone gets loaded it patches on the different window functions. The exception basically states the same.

Having said that it is 100% possible to have angular elements inside another Angular application. All we need to take care of is loading zone js only once in parent/shell/host app 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 :

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 run multiple Angular Elements (Web Components) and use in a Angular shell SPA.

NOTE the other options is to eject from zonejs but that way you will have to take care of ChangeDetetction manually.

Thanks

Marinetti answered 20/12, 2019 at 11:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.