web-dev-qa-db-ja.com

Zone.jsは、ZoneAwarePromise `(window | global).Promise`がカスタム要素で上書きされたことを検出しました

angularカスタム要素機能(element.js)を使用して小さなアプリを作成し、そのelement.jsファイルを、開発サーバー(ng serve)のindex.htmlにある別のangular(parent)アプリにインポートしました要素機能は正常に動作しますが、プロダクションモード(ng build --prod)では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>

エラーZone.jsはZoneAwarePromise (window|global).Promiseは上書きされました。最も可能性の高い原因は、PromiseポリフィルがZone.jsの後にロードされたことです(zone.jsがロードされている場合、Polyfilling Promise APIは必要ありません。ロードする必要がある場合は、zone.jsをロードする前に行ってください)。

10
selvin john

Angular Elementsを使用して変更の検出を自分で処理する場合は、頭痛の負荷を軽減するためにZoneを削除することをお勧めします。

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

次に、PolyFillsから必ず削除してください。

1
Andrew Dover

Zonejsを複数回ロードすることはできません。その理由は、一度zoneが読み込まれると、さまざまなウィンドウ関数にパッチが適用されるためです。例外は基本的に同じです。

とはいえ、別のangularアプリケーション内にAngular要素を含めることは100%可能です。私たちが注意する必要があるのは、ゾーンjsを親/シェル/ホストアプリで一度だけロードし、それをすべてのWebコンポーネント(角度要素)で共有することだけです。

複数の要素をブートストラップするときに、以下のようにすでにロードされている場合、zonejsをロード/パッチしないロジックを追加できます。

すべてのAngular要素のpolyfill.tsからzonejs polyfillを削除します

Main.tsレベルでファイルを作成します。 bootstraper.tsとしましょう:

すべてのAngular要素のpolyfill.tsからzonejsポリフィルを削除

main.tsレベルでファイルを作成します。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);
    });
  }
}

そしてmain.tsでは、bootstrapロジックを以下のように変更できます。

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

このようにして、複数のAngular要素(Webコンポーネント)を確実に実行し、AngularシェルSPAで使用できます。

[〜#〜] note [〜#〜]他のオプションはzonejsから排出することですがそうすれば、手動でChangeDetetctionを処理する必要があります。

ありがとう

0
Siddharth Pal