4

I have recently created a native web component which is working well in all browsers. I moved this web component into an Angular 6 application and all works as expected. I then tried to extend a native HTML element which again worked perfectly except when I brought it into my Angular 6 application.

Using the examples from Mozilla I will try and illustrate my issue. Using the following trying to extend a native 'p' element:

// Create a class for the element
class WordCount extends HTMLParagraphElement {
  constructor() {
    // Always call super first in constructor
    super();

    // count words in element's parent element
    var wcParent = this.parentNode;

    function countWords(node){
      var text = node.innerText || node.textContent
      return text.split(/\s+/g).length;
    }

    var count = 'Words: ' + countWords(wcParent);

    // Create a shadow root
    var shadow = this.attachShadow({mode: 'open'});

    // Create text node and add word count to it
    var text = document.createElement('span');
    text.textContent = count;

    // Append it to the shadow root
    shadow.appendChild(text);


    // Update count when element content changes
    setInterval(function() {
      var count = 'Words: ' + countWords(wcParent);
      text.textContent = count;
    }, 200)

  }
}

// Define the new element
customElements.define('word-count', WordCount, { extends: 'p' });
<p is="word-count">This is some text</p>

By taking that same code and putting it into an Angular 6 application, the component never runs. I put console log statements in the constructor and connectedCallback methods and they never trigger. If I remove the {extends: 'p'} object and change the extends HTMLParagraphElement and make it an extend HTMLElement to be an autonomous custom element everything works beautifully. Am I doing something wrong or does Angular 6 not support the customized built-in element extension?

5
  • I don't know how Angular is supposed to "support" customized built-ins - to any framework/library/third-party code that should just be an HTML element which should ofc work. It not working to me rather is an indicator of your transpilation/bundling setup not working properly. Are you using Typescript to compile your webcomponent along with Angular code? Commented Dec 4, 2018 at 3:41
  • My understanding is that custom elements are supported at the browser level (in certain browsers) and I have gotten this exact code to work by just importing it into a plain HTML file. I was under the assumption that moving this code to Angular would continue to work without the need or any sort of Transpiling or building. Again, with my example, if I create a custom element and do not try to extend an element, it works in Angular. The second I try using the {extends: 'p'} it stops working. Didn't think I would need to do anything specific to get it to work in Angular. Commented Dec 4, 2018 at 14:03
  • Can you create a StackBlitz showcasing the issue? Commented Dec 4, 2018 at 18:12
  • Sure - here is an example of it working outside of angular stackblitz.com/edit/customized-built-in-element Here is the same component in Angular not working (I also added CUSTOM_ELEMENTS_SCHEMA) stackblitz.com/edit/angular-52fs9p Commented Dec 4, 2018 at 21:35
  • Let me know if my answer helped you as I'm also curious as of how to get it to work. Commented Dec 5, 2018 at 17:21

2 Answers 2

5

I assume the reason is the way that Angular creates those customized built-in elements when parsing component templates - it probably does not know how to properly do that. Odds are it considers is a regular attribute which is fine to add after creation of the element (which it isn't).

First creating the element and then adding the is-attribute will unfortunately not upgrade the element.

See below example: div#d has a non-working example of that customized input.

customElements.define('my-input', class extends HTMLInputElement {
  connectedCallback() {
    this.value = this.parentNode.id
    this.parentNode.classList.add('connected')
  }
}, {
  extends: 'input'
})

document.addEventListener('DOMContentLoaded', () => {
  b.innerHTML = `<input type="text" is="my-input">`

  let el = document.createElement('input', {
    is: 'my-input'
  })
  el.type = 'text'
  c.appendChild(el)

  // will not work:
  let el2 = document.createElement('input')
  el2.setAttribute('is', 'my-input')
  el2.type = 'text'
  d.appendChild(el2)
})
div {
  border: 3px dotted #999;
  padding: 10px;
}

div::before {
  content: "#"attr(id)" ";
}

.connected {
  background-color: lime;
}
<div id="a"><input type="text" is="my-input"></div>
<div id="b"></div>
<div id="c"></div>
<div id="d"></div>

So to get it to work with Angular, hook into the lifecycle of your Angular component (e.g. onInit() callback) and pick a working way to create your element there.

Sign up to request clarification or add additional context in comments.

2 Comments

This is perfect and exactly what I was looking for. I really appreciate all the help @connexo and understanding my rambling. This really helped and I would upvote 10 times if I could.
same problem with Svelte.
0

The is= extension syntax was in an early draft of the web components spec. It's deprecated and only works for some elements in some browsers in some contexts, and will be fully removed at some point.

The reason is that, while extending existing components was an early goal, there are security issues to being able to completely override how native browser components are rendered.

<p is="word-count"> shouldn't work, but still does in Chrome.

Instead use <word-count></word-count>, class WordCount extends HTMLElement and customElements.define('word-count', WordCount).

If you want to have a fallback you have 3 options:

  1. Put the fallback inside the element and don't use <slot>
  2. Render something else until await customElements.whenDefined('word-count')
  3. Use CSS word-count:not(:defined) style rule to render something else

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.