web-dev-qa-db-fra.com

Zone.js a détecté que ZoneAwarePromise `(window | global) .Promise` a été écrasé dans l'élément personnalisé

J'ai créé une petite application en utilisant angular (element.js), j'ai importé ce fichier element.js dans une autre application angulaire (parent) dans index.html, dans le serveur de développement (ng serve) La fonctionnalité d'élément fonctionne bien, mais en mode production (ng build --prod), obtenir cette erreur dans le fichier element.js.

@ 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>

Erreur Zone.js a détecté que ZoneAwarePromise (window|global).Promise a été écrasé. La cause la plus probable est qu'un polyfill Promise a été chargé après Zone.js (l'API Polyfilling Promise n'est pas nécessaire lorsque zone.js est chargé. Si vous devez en charger un, faites-le avant de charger zone.js.).

10
selvin john

Pour vous épargner un tas de maux de tête, il est conseillé de supprimer Zone lors de l'utilisation des éléments Angular et de gérer vous-même la détection des modifications.

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

Assurez-vous ensuite de le retirer de vos PolyFills.

1
Andrew Dover

Nous ne pouvons pas charger plusieurs fois zonejs. La raison est qu'une fois zone chargé, il corrige les différentes fonctions de la fenêtre. L'exception stipule essentiellement la même chose.

Cela dit, il est possible à 100% d'avoir des éléments angular dans une autre Angular. Tout ce dont nous avons besoin pour prendre soin est de charger la zone js une seule fois dans le parent)/Shell/Host app et le partager sur tous les composants Web (éléments angulaires).

Tout en amorçant plusieurs éléments, nous pouvons ajouter la logique de ne pas charger/patcher les zonesjs s'ils sont déjà chargés comme ci-dessous:

Supprimer zonejs polyfill de polyfill.ts pour tous Angular Elements

Créez un fichier au niveau main.ts. Disons bootstraper.ts:

Supprimer zonejs polyfill de polyfill.ts pour tous Angular Elements

Créez un fichier dans main.ts niveau. Disons 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);
    });
  }
}

Et en main.ts nous pouvons changer la logique bootstrap en celle ci-dessous:

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();

De cette façon, nous pouvons certainement exécuter plusieurs Angular Elements (Web Components) et les utiliser dans un Angular Shell SPA).

[~ # ~] note [~ # ~] les autres options sont d'éjecter de zonejs mais de cette façon, vous devrez prendre soin de ChangeDetetction manuellement.

Merci

0
Siddharth Pal