Angular Elements生成的Web Components在Chrome中报Cannot read properties of undefined错误
Cannot read properties of undefined (reading 'create')错误 问题重现
使用Angular Elements(依赖版本:@angular/animations 14.2.7、@angular/cdk 14.2.5、@angular/common 14.2.7、@angular/compiler 14.2.7、@angular/core 14.2.7、@angular/elements 14.2.7)生成两个Web Components:web-comp-1和web-comp-2,添加到HTML页面后仅在Chrome浏览器中触发控制台错误:
Uncaught TypeError: Cannot read properties of undefined (reading 'create')
at new m.(web-comp-1.js:3:2706)
at new t (web-comp-1.js:3:12616)
对应代码行逻辑为:(o = (u = F && 1 === a.create.length) ? Reflect.construct,该问题在StackBlitz沙箱环境中无报错,仅本地Chrome复现,Firefox浏览器正常。
原因分析
Chrome对自定义元素构造函数的执行上下文和全局变量隔离要求比Firefox更严格,Angular 14.2早期版本的Elements在生成Web Component构造函数时,错误地依赖了一个未被正确初始化的内部全局变量a。当多个Web Components脚本在同一页面加载时,该变量可能被后加载的脚本覆盖或未完成初始化,导致a.create访问失败。StackBlitz的沙箱环境会自动隔离每个脚本的全局作用域,因此不会触发该问题。
解决方案
1. 升级Angular补丁版本
将Angular相关依赖升级到14.2.x的最新补丁版(如14.2.12),官方已修复该Chrome兼容性问题:
npm install @angular/animations@~14.2.12 @angular/cdk@~14.2.12 @angular/common@~14.2.12 @angular/compiler@~14.2.12 @angular/core@~14.2.12 @angular/elements@~14.2.12
2. 隔离Web Components打包作用域
确保两个Web Components使用独立的打包配置,避免共享全局内部变量:
- 在
angular.json中为每个组件创建单独的项目配置,分别打包生成独立的脚本文件 - 或使用自定义Webpack配置,为每个组件设置独立的全局命名空间
3. 添加构造函数兼容补丁
在定义自定义元素时,手动覆盖构造函数以处理Reflect.construct的兼容问题:
import { createCustomElement } from '@angular/elements'; import { Injector } from '@angular/core'; import { WebComp1Component } from './web-comp1.component'; // 常规定义自定义元素 const WebComp1Element = createCustomElement(WebComp1Component, { injector: injector }); window.customElements.define('web-comp-1', WebComp1Element); // Chrome兼容补丁 const originalCtor = window.customElements.get('web-comp-1'); window.customElements.define('web-comp-1', class extends originalCtor { constructor(...args) { try { super(...args); } catch (e) { const instance = Reflect.construct(originalCtor, args, new.target); Object.setPrototypeOf(this, instance.prototype); return instance; } } });
4. 调整脚本加载顺序
确保HTML页面中两个Web Components脚本的加载顺序正确,避免变量污染:
<!-- 先加载第一个组件脚本 --> <script src="web-comp-1.js"></script> <!-- 再加载第二个组件脚本 --> <script src="web-comp-2.js"></script>
或使用defer属性异步加载,确保脚本加载完成后再初始化元素:
<script src="web-comp-1.js" defer></script> <script src="web-comp-2.js" defer></script>
内容的提问来源于stack exchange,提问作者Tony

