[proposal] Passing arguments to Custom Element's createdCallback (constructor)


#1

The API is somewhat limited in regards to creating Custom Elements. createdCallback doesn’t take any arguments (and nor do we get to define and use our own constructor). It’d be nice if document.createElement would take extra arguments to pass to the new Element’s createCallback. For example:

document.createElement('foo-bar', arg1, arg2, arg3)

Then, arg1, arg2, and arg3 would be passed to the element’s createdCallback, which might look like this:

// ...
    createdCallback(arg1, arg2, arg3) {
      // do something with the args.
    },
// ...

I think this is a simple and much need extension to the API.

The idea here can possibly be combined with https://discourse.wicg.io/t/809. For exampmle, maybe arguments to createdCallback are third and beyond, while the second argument contains attributes:

document.createElement('foo-bar', {
  attr1: '1',
  attr2, '2'
}, arg1, arg2, arg3)

or something similar. The is-"" attribute needs to be taken into consideration too. Also note that the arguments can be anything, not limited to strings like attributes are.

Any thoughts?


#2

I’m really confused where is this createdCallback defined?

If this is a method that is passed to the createElement options arguments then there’s no need to pass, just use scope:

var arg1, arg2, arg3;
document.createElement('foo-bar', {
  createdCallback: function() {
    // this scope can access arg1, arg2, arg3;
  }
});

or if I’m missing something then I’d prefer:

document.createElement('foo-bar', {
  onCreatedArgs: [arg1, arg2, arg3]
});

#3

createdCallback is part of the Custom Elements API. It gets called after your element is constructed, so we don’t have to run synchronous constructor code in the middle of parsing.


#4

@Edwin_Reynoso In your example, every single element created will have access to the same exact arguments. The reason for passing arguments during creation is for each instance to be able to accept arguments individually.

Whoever imports the custom elements in your example won’t be able to set those args when creating elements, which means the code isn’t as re-usable. For example: consider two cases, where each case shows one file that exports, and one that imports:

case 1

var arg1, arg2, arg3;
export default document.createElement('foo-bar', {
  createdCallback: function() {
    // this scope can access arg1, arg2, arg3;
  }
});
import FooBar from './foo-bar'

// how does user of your custom element (suppose they imported it from NPM) set the args now?:
let el = new FooBar()

case 2

In this case, the user decides what the args are (the component is re-usable with different characteristics depending on the arguments):

export default document.createElement('foo-bar', {
  createdCallback: function(arg1, arg2, arg3) {
    // this scope can access arg1, arg2, arg3;
  }
});
import FooBar from './foo-bar'

let el = new FooBar(1, 2, 3)

#5

Hi, I implemented Custom Elements in Blink so I am pretty familiar with it.

One wrinkle with this idea is that createElement already takes a second argument, a dictionary of element creation options. So additional arguments to createElement should work with that. Because the argument is a dictionary, trying to switch on the type of the argument probably would not work.

Another wrinkle: createdCallback has been removed from the spec; now you can use a constructor. I’m implementing this in Blink at the moment. I think passing arguments to this constructor will just work:

class MyElement extends HTMLElement {
  constructor(a, b) {
    super();
    this._a = a;
    this._b = b;
  }
}
window.customElementsRegistry.define('my-element', MyElement);
let e = new MyElement('foo', 'bar');
console.log(e._a); // logs 'foo'

However when the element is created by other means (the HTML parser, createElement, etc.) the constructor will be invoked without any additional arguments. HTH!


#6

Ah, nice! Maybe createElement options can take args? f.e.:

document.createElement('foo-bar', {
  arguments: [arg1, arg2, arg3],
  // ... other stuff ...
})

On a related note, It’d be nice to have some way to scope Custom Elements to a Web Component (probably scoped to ShadowDOM roots since those already provide other forms of encapsulation that a Custom Element take advantage of). The idea is distantly similar to component scoping that React andsimilar libraries provide (just the mechanism works differently, my idea relying on a registry existing on shadow roots, while React/etc relying on JavaScript lexical scoping). I wrote about this possible idea in the w3c Web Components GitHub issue tracker.


#7

I think elements always need to be prepared to be created without arguments. How would the HTML parser know what arguments to call the constructor with?

When running script, you could write something like what you have above by retrieving the constructor with get and calling it directly. This also forces you to think about what to do if the element is not defined.

Yeah, this is an interesting idea. It might make sense to start a thread here with some more details (link to what React does, explain the benefits of this.) There’s some back and forth right now about adopting custom elements and it may make sense to wait and see what the conclusion to that is. I imagine you could treat components a bit like mini-documents.


#8

I believe this problem might be solved in Custom Elements v1, where an ES6 class’ actual constructor method is used instead of a createdCallback. So, we can simply pass anything we want into the constructor.


#9

Here’s the v1 spec about constructors: https://w3c.github.io/webcomponents/spec/custom/#custom-element-conformance

It doesn’t explicitly say that we can pass arguments to the constructor, but I’m guessing if it works like all other class constructors then we can pass arguments to it.

TLDR, in v1 there is no more createdCallback and instead now we use the constructor which follows the ES6 spec (awesome!).


#10

If you’re interested you can install Chrome Canary and run it with --enable-blink-features=CustomElementsV1 and try it. This should work (but not much else.)