关于angular:Zone.js检测到ZoneAwarePromise`(window | global).Promise`已在自定义元素中被覆盖

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

我使用角度自定义元素功能(element.js)创建了一个小应用程序,将那个element.js文件导入到index.html中的另一个angular(parent)应用程序中,在开发服务器(ng serve)元素功能正常,但是在生产模式下 (ng build --prod)在element.js文件中得到此错误。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
  }
}
1
2
3
4
5
6
7
8
9
angular (parent app)
index.html
<!doctype html>
<html lang="en">
<body>
    </app-root>
    <script src="./assets/elements.js">
</body>
</html>
1
2
3
4
5
6
7
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;
1
2
app.component.html
 <test-card [data]="{id:"foo"}"></test-card>

错误Zone.js检测到ZoneAwarePromise (window|global).Promise已被覆盖。 最有可能的原因是在Zone.js之后加载了Promise polyfill(加载zone.js时不需要填充Promise api。如果必须加载,则在加载zone.js之前执行。)。


为了避免麻烦,建议在使用Angular Elements时删除Zone并自己进行更改检测。

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

然后确保将其从PolyFills中删除。


我们不能多次加载zonejs。原因是一旦zone被加载,它将在不同的窗口函数上进行修补。异常基本上表示相同。

话虽这么说,在另一个Angular应用程序内部也有100%的可能包含角度元素。我们需要照顾的只是在父/ shell /主机应用程序中仅将区域js加载一次,并在所有Web组件(Angular Elements)中共享它。

在引导多个元素时,我们可以添加不加载/修补zonejs的逻辑(如果已经加载的话),如下所示:

从polyfill.ts中删除所有Angular Elements的zonejs polyfill

在main.ts级别中创建一个文件。假设bootstraper.ts:

从polyfill.ts中删除所有Angular Elements的zonejs polyfill

创建main.ts级别的文件。假设bootstraper.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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 = 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 {
    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中,我们可以将引导逻辑更改为以下逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 Elements(Web组件)并在Angular shell SPA中使用。

注意其他选项是从zonejs弹出,但是那样您将必须手动处理ChangeDetetction。

谢谢