首页 > 解决方案 > 扩展的自定义元素在 Angular 2+ 中不起作用

问题描述

我无法让 Angular(2+,不是 AngularJS)与我的扩展自定义元素配合得很好,它的定义如下:

  class FancyButton extends HTMLButtonElement {
    connectedCallback() {
      this.innerText = `I'm a fancy-button!`;
      this.style.backgroundColor = 'tomato';
    }
  }

  customElements.define("fancy-button", FancyButton, {
    extends: "button"
  });

并像这样使用:

<button is="fancy-button">Fancy button here</button>

根据此 Google Developer 资源,该定义完全符合网络标准: https ://developers.google.com/web/fundamentals/web-components/customelements#extend

它在普通网络设置和 React 中运行良好,但 Angular 忽略它并显示一个标准按钮,显然忽略了is="fancy-button"属性。

这是一个堆栈闪电战,展示了这一点。一个花哨的按钮在 Angular 范围 (index.html) 之外并且工作正常。另一个按钮在 Angular 范围内 (app.component.html) 并且不工作。

为什么哦为什么?

标签: javascriptangular

解决方案


有两种类型的自定义元素

  1. 自治自定义元素,这些类extend HTMLElement

  2. 自定义的内置元素,它们是扩展特定类型元素的类,例如extend HTMLButtonElement.

Angular 不支持自定义的内置元素(下面有更多详细信息)。Safari 也不支持它们(截至 2020 年 9 月:https ://caniuse.com/#search=custom%20elements )。

您的示例是自定义的内置元素。对我有用的解决方法是将任何自定义内置元素重写为围绕内置元素的自治自定义元素。这给了我我想要的封装,它给了我一种自定义内置的方法,它适用于 Angular 和 Safari。

对于上面的示例,翻译为:

class FancyButtonToo extends HTMLElement {
  connectedCallback() {
    const buttonElement = document.createElement('button');
    this.appendChild(buttonElement);
    buttonElement.innerText = "Fancy button #2 here!";
    buttonElement.style.backgroundColor = 'tomato';
  }
}

customElements.define("fancy-button-too", FancyButtonToo);

(该项目还需要schemas: [ CUSTOM_ELEMENT_SCHEMAS]添加到app.module.ts)。完整代码在这里:stackblitz,它呈现如下(原始“花式按钮”留作比较):

在此处输入图像描述

附加信息

问:我们是否确定 Angular 不支持自定义的内置元素(例如,有一些我们不知道的晦涩配置)?

答:我们确定:https ://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-autonomous-example 上的规范文档 详细介绍了自治自定义元素和自定义元素之间的区别内置元素。一个重要的区别在于元素的程序化构造:

// Built-in Elements and Autonomous Custom Elements are created like this:
el = createElement(name);
// examples
el = createElement("button"); // normal built-in button
el = createElement("fancy-text"); // a custom element

// Customized Built-in Elements are created like this:
el = createElement(built-in-name, { is: custom-built-in-name });
// example
el = createElement("button", { is: "fancy-button" });

相关的 Angular 模板渲染代码位于 https://github.com/angular/angular/blob/master/packages/platform-b ​​rowser/src/dom/dom_renderer.ts 并且在当前版本的 Angular 中,您将寻找:

class DefaultDomRenderer2 implements Renderer2 {
  /* ... */
  createElement(name: string, namespace?: string): any {
    if (namespace) {
      return document.createElementNS(NAMESPACE_URIS[namespace] || namespace, name);
    }
    return document.createElement(name);
  }
  /* ... */
}

Angular 渲染器目前没有将第二个参数传递给createElement();所需的额外代码。它不能创建自定义内置元素。


推荐阅读