48

I'm trying to detect whether a custom element with a specific name was registered or not. Is there a way to make such check?

Or is there a way to get list of registered custom elements?

I do document.registerElement, but what else is there? Is it one-way API?

dy_
  • 6,462
  • 5
  • 24
  • 30

10 Answers10

58

There is a way to check whether an element was registered. Registered elements have their own constructors, while unregistered ones would use plain HTMLElement() for constructor (or HTMLUnknownElement() whether the name is not valid, but this is out of scope of the question):

document.registerElement('x-my-element');
document.createElement('x-my-element').constructor
//⇒ function x-my-element() { [native code] }
document.createElement('x-my-element-not-registered').constructor
//⇒ function HTMLElement() { [native code] }

That said, the checker might look like:

var isRegistered = function(name) {
  return document.createElement(name).constructor !== HTMLElement;
}

Or, with syntactic sugar:

String.prototype.isRegistered = function() { 
  return document.createElement(this).constructor !== HTMLElement; 
}
'x-my-element'.isRegistered()
//⇒ true
'xx-my-element'.isRegistered()
//⇒ false

The mostly careful version:

String.prototype.wasRegistered = function() { 
  switch(document.createElement(this).constructor) {
    case HTMLElement: return false; 
    case HTMLUnknownElement: return undefined; 
  }
  return true;
}
'x-my-element'.wasRegistered()
//⇒ true
'xx-my-element'.wasRegistered()
//⇒ false
'xx'.wasRegistered()
//⇒ undefined

There is no way to access a list of registered elements, AFAIK.

BTW, I still think that the try-catched registration (as proposed by @stephan-muller) suits your needs better.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • 1
    I'm not sure if my answer suits his needs better, but this is a great answer with useful additional information. Thanks! – Stephan Muller Feb 01 '15 at 15:54
  • I am glad that you found the answer useful. – Aleksei Matiushkin Feb 02 '15 at 09:50
  • Great answer. Just be careful that while testing if an element is registered, `document.createElement(name).constructor` part actually creates the element if it is already registered, and you could find yourself searching for why ready event of your Polymer element is firing twice :) – Mehmet AVŞAR Nov 26 '16 at 19:49
  • @synk The check above has nothing to do with an _operating system_. It depends on browser only. – Aleksei Matiushkin Apr 10 '17 at 13:11
44

Since custom elements is now part of the latest standard, I thought I'd share how to do this in 2017+:

Note: the document.registerElement function has been deprecated in favor of customElements.define().

customElements is defined as a global in window. There are three methods defined:

  1. define
  2. get
  3. whenDefined

get is the important one here. get takes a string of the element name and returns the constructor for the named custom element, or undefined if there is no custom element definition for the name.

So in 2017+ to check if an element has been registered do you:

const myElementExists = !!customElements.get('my-element');

I'm not sure if there's a way to get a list of defined elements however.


NOTE: this doesn't work in IE. See here for browser compatibility

Rico Kahler
  • 17,616
  • 11
  • 59
  • 85
30

EDIT (2021): This no longer works as /deep/ was removed a few years ago.


Combining a few of the above approaches you can iterate over everything in use and spit out a unique list of custom (and registered) elements:

function isRegistered(name) {
  return document.createElement(name).constructor.__proto__ !== window.HTMLElement;
}

var allElems = document.querySelectorAll('html /deep/ *');
var nodeNames = [].map.call(allElems, el => el.nodeName.toLowerCase())
                    .filter((value, index, self) => self.indexOf(value) === index)

console.log('all elements', nodeNames);
console.log('registered, custom elements', nodeNames.filter(isRegistered))

enter image description here

Paul Irish
  • 47,354
  • 22
  • 98
  • 132
10

There doesn't seem to a way to see all registered elements at the moment, but there is a way to check whether or not an element has been registered already: wrap the register in a try...catch block:

try {
    document.registerElement('x-my-element');
} catch(e) {
    console.log('already exists', e);
}

Run this twice in your console and you'll see the error logged.

This does have a drawback if you simply want to check whether or not it was registered though: if it was not, it will be after running this. There also isn't a way to unregister an element it seems.

Stephan Muller
  • 27,018
  • 16
  • 85
  • 126
7

While I am not sure it applies to other Web Component frameworks, while using Polymer in Chrome, I have a CustomElements object to the window object. The CustomElements object has a key/value collection of all registered custom elements called registry.

function isRegistered(name) {
    if (window.CustomElements && window.CustomElements.registry)
        return name in window.CustomElements.registry;
    return undefined;
}
Fuzzical Logic
  • 12,947
  • 2
  • 30
  • 58
  • 2
    It seems that this is a result of Polymer keeping track of which elements it registers. Given the lack of a native way to fetch a list of custom registered elements, it would actually be a good idea to keep track of them yourself while registering new elements. However, this doesn't necessariy solve the original problem here, as it's not clear from the question whether or not he registers the elements himself. There might be some library/framework included in his app that does the registering without keeping track. – Stephan Muller Feb 01 '15 at 15:56
  • Agreed. Just thought it might be useful for others looking at this, as some Polymer users might end up here. – Fuzzical Logic Feb 02 '15 at 01:53
3

As written already on the Polymer's Slack channel, this is a dirty one that can make the job:

function isElementRegistered(elementId) {
  return Polymer.telemetry.registrations.find(function(item) { return item.is === elementId })
}

Not sure how much Polumer.telemetry.registrations is reliable though (haven't seen it on the doc) and Array.prototype.find is not cross-browser!

lzzluca
  • 706
  • 5
  • 14
2

Here is one way you get get a list of all the registered customElements using ES6

// Get all elements
const elements = document.querySelectorAll('*')

// Create an array from elements
const elementArray = Array.from(elements)

// Map to node names
const nodeNames = elementArray.map(element => element.nodeName.toLowerCase())

// Filter by which ones are registered
const allCustomElementNames = nodeNames.filter(customElements.get.bind(customElements))
Jesse Onolemen
  • 1,277
  • 1
  • 15
  • 32
1

in scenarios where custom element classes (constructors) self-register an element, it is sufficient to check for the presence of the class

Markus
  • 668
  • 7
  • 27
0

I was looking for a list of all registered custom elements. Based on answers here, came up with something like this:

    let original = customElements.define;

    window.customTags = [];

    customElements.define = function () {
      console.log("custom elements hijacked", arguments);

      window.customTags.push({ tag: arguments[0], ctor: arguments[1] })

      original.apply(customElements, arguments);
    }

    setTimeout(() => {
      console.log(window.customTags);
    }, 1000)
nukle
  • 83
  • 5
0

To answer the 1st question you could simply use the customElements.get() method as follow :

function isRegistered( name ) {
    return ( customElements.get( name ) != undefined ) ? true : false;
}

Simply call that method with your custom element name as a string it will return :

  • true if that name is alreay used
  • false if the name is available (no custom element registered with that name).

For the 2nd question it looks like that the answers that consist in retrieving page elements and filtering them to only have the custom elements will not work as they will not return registered custom elements that have not been used yet.

You could test that with this function :

function getRegisteredCustomElements() { // This is a failing approach !!
    let registered = []; 
    Array.from(document.getElementsByTagName('*')) 
    .forEach( (tag) => {
        let tagName = tag.tagName.toLowerCase(); // current tag name in lower case
    
        if ( registered.findIndex( tag => tag == tagName ) == -1 // avoid duplicates
                && tagName.includes('-') ) { // a custom element name must use at least one '-'
            registered.push( tagName ); 
        }

    });
    return registered;
}

This function retrieves all the tagNames in the page before filtering them to return an array that contains as unique entries the different custom elements names found in the page.

While it may seems to work if a custom element is already registered but is not instantiated it will not return it's name so it is failing.

Darkosphere
  • 107
  • 8