Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert HTMLElement and friends to classes to support Web Components/Custom elements #574

Open
sedwards2009 opened this issue Aug 30, 2014 · 33 comments
Labels
Domain: lib.d.ts The issue relates to the different libraries shipped with TypeScript In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@sedwards2009
Copy link

Please model HTMLElement and its subclasses as classes instead of interfaces in lib.d.ts to make it possible to subclass them.

My motivation is custom elements and web components. To create a custom element you need to create a subclass of HTMLElement and register it with the browser. Currently, HTMLElement etc are interfaces and can't be subclassed. I've been using a hacked lib.d.ts in my pet TypeScript project which uses web components.

Tutorial and info about how to use custom elements is here: http://www.html5rocks.com/en/tutorials/webcomponents/customelements/

@am11
Copy link

am11 commented Aug 31, 2014

👍

Another reason to implement support for abstract class in TypeScript: #6 😃

@danquirk
Copy link
Member

danquirk commented Sep 2, 2014

See #299, we'll be revisiting lib.d.ts changes in the near future and try to tackle some of these then.

@jamiewinder
Copy link

Any update on this? Is there a clean way to write custom elements in TypeScript at the moment?

@sothmann
Copy link

sothmann commented May 5, 2015

Any update on this one?

@mhegazy
Copy link
Contributor

mhegazy commented May 5, 2015

We have a few suggestions to make this work.. we first need #2961, #2959 and possibly,
#2957 to make the change to the library..

Also related #2341 to ensure native types with uncallable constructors are modeled correctly, e.g. Symbol or HTMLElements.

#1168 tracks doing the same for non-DOM native types.

@Ciantic
Copy link

Ciantic commented May 7, 2015

👍

For what it's worth, here is my ancient hack to get inheritance working with custom elements: https://gist.github.com/Ciantic/9db1b6281bd7a743ffb5

@benlesh
Copy link

benlesh commented May 14, 2015

This is a killer. with babel/ES6 I can easily do the following to create a WebComponent:

class MyComponent extends HTMLElement {

}

But in TypeScript it complains that I can't extend because HTMLElement is not a class. It is indeed a class.

@mhegazy
Copy link
Contributor

mhegazy commented May 21, 2015

This should be fixed in TypeScript 1.6

@mhegazy mhegazy added Committed The team has roadmapped this issue and removed Revisit An issue worth coming back to labels May 21, 2015
@mhegazy mhegazy added this to the TypeScript 1.6 milestone May 21, 2015
@mhegazy
Copy link
Contributor

mhegazy commented May 21, 2015

Also see #1168.

@basarat
Copy link
Contributor

basarat commented Jun 23, 2015

This can be closed thanks to : #3516 just tested this with atom-typescript@4.6.0

class MyComponent extends HTMLElement {    
}

image

@mhegazy mhegazy closed this as completed Jun 23, 2015
@mhegazy
Copy link
Contributor

mhegazy commented Jun 23, 2015

With the change to allow extending expressions (#3516), there is no need for the definition in the lib.d.ts to be classes. we could reconsider and change the, to classes in the future.

@mhegazy mhegazy added Revisit An issue worth coming back to and removed Committed The team has roadmapped this issue labels Jun 23, 2015
@danquirk danquirk reopened this Jun 23, 2015
@mhegazy mhegazy modified the milestone: TypeScript 1.6 Jul 1, 2015
@gertcuykens
Copy link

Can you review the above reference to make Polymer more typescript friendly please, I think nippur72 has everything covert but I would appreciated it to confirm we are going in the right direction.

@aphex
Copy link

aphex commented Aug 1, 2015

I think I am missing something but even though you can now extend HTMLElement I can't seem to actually use it. This is because of the constructor issue, Typescript forces all derived classes to call super and that will throw an error on HTMLElement.

I am getting a 'Illegal constructor' error thrown anytime the class is created.

@nippur72
Copy link

@aphex: HTML elements should not be instantiated with new, but with document.createElement().

The fix for this issue is important because it lets you have the whole HTMLElement interface in your subclass, enabling intellisense and all the rest. Good for working with WebComponents.

@aphex
Copy link

aphex commented Sep 14, 2015

@nippur72 I understand that, and actually thats the exact situation I am trying to use this in. Applying a class for a web component. The issue is the compiler throws a warning that the 'constructor is illegal' because it is not calling super.

There seems to be a requirement that derived classes call there parents constructors.

I am looking to make classes that are truly HTMLElements, in the sense that the extend them, not just composite or decorate one.

@nippur72
Copy link

@aphex unfortunately WebComponents doesn't work with instances of HTMLElement, but only with plain javascript objects (that are later mixed to a HTMLElement). That is the source of the problem.

So all you can do is to simulate that these objects are extensions of HTMLElements, in order to have IDE integration (intellisense, static typing etc). But it's just a trick. You might want to check PolymerTS which indeed does the above mentioned "trick".

Also consider that in order to make a HTMLElement work with the DOM, it has to be created with document.createElement() and not with new (as far as I know). So I'm afraid your goal of making "true" HTMLElements can't be achieved.

@aphex
Copy link

aphex commented Sep 14, 2015

@nippur72 Actually thats not the case at all, I have all the working code for this already the only problem i am having is the warning from TS compiler. You can also create an element in markup, createElement doesn't have to be in the mix at all.

In Javascript an instance (Class or not) of something is just an object, everything is just an object :)

The concept is pretty straight forward. Simply define a class that extends HTMLElement, create that class (new ClsName) and use that instance as the proto of the registered element. Merge in a a little createdCallback magic and you can get WebComponents working very well with classes, and extending anything (including HTMLElement). The benefit here is I get all that code completion and such because of the IDE's understanding of inheritance.

I am using a similar method as Polymer to rip out a template and CSS its just instead of having to include it all in one place you can separate everything. Templates are a jade file, CSS is SASS and code is a class with full inheritance.

Just because at the end of the day the WebComponent needs to be portable and deliverable in a nice packed file it doesn't mean we need to develop that way. Thats what Gulp is for, at least I don't think so :)

So essentially the help I need is making it so typescript understands there are some Classes that do not require constructors being called, I just wanna get rid of this dang warning. If I extend HTMLElement I simply want to skip its constructor and only call my own.

@nippur72
Copy link

@aphex what doesn't convince me is the fact that WebComponents doesn't use directly the object-prototype you feed it. As far as I know, WebComponents creates its own instance of HTMLElement and then extends it with members from the object you passed to it. So your efforts to give it an HTMLElement are ignored.

I agree 100% about the missing super() call warning, I wish too there was a way to tell the class doesn't need to call to constructor. Is there an open issue about it?

@aphex
Copy link

aphex commented Sep 15, 2015

@nippur72 Thats very interesting, so technically they are always going to extend HTMLElement. So really there is no need for a class to extend HTMLElement at all if it is going to be a WebComponent, except for code completion in the IDE.

So if it is being used as an interface how is that working? In order for it to be an interface wouldn't one need to make all the methods and such from HTMLElement? In the case of 'MyClass implements HTMLElement'.

This of course changes my whole mentality on what I was building :) probably unneeded for me to deal with HTMLElement inheritance.

@mhegazy
Copy link
Contributor

mhegazy commented Dec 3, 2015

I agree 100% about the missing super() call warning, I wish too there was a way to tell the class doesn't need to call to constructor. Is there an open issue about it?

@nippur72 do you mind filing an issue for this.

@nippur72
Copy link

nippur72 commented Dec 3, 2015

@mhegazy I changed my mind a bit regarding super(), so I've opened this other issue: #5910

Btw, regarding this original issue, HtmlElement is now a variable and can be used with the feature or "extending expressions".

@mhegazy
Copy link
Contributor

mhegazy commented Dec 3, 2015

i think they should all be classes... eventually.

@mhegazy mhegazy added the Domain: lib.d.ts The issue relates to the different libraries shipped with TypeScript label Dec 10, 2015
@mhegazy mhegazy added In Discussion Not yet reached consensus and removed Revisit An issue worth coming back to labels Feb 20, 2016
@0815fox
Copy link

0815fox commented Jul 11, 2016

I just ran into this issue as well. The problem is, that people are mislead somehow, because they see the examples on MDN or on Custom Elements spec - working draft, where it is suggested to create an instance of the new, own webcomponent like this:

var myBtn = new MySaveBtn;
document.querySelector('#placeholder').appendChild(myBtn);

Or from the working draft: "Finally, we can also use the custom element constructor itself. That is, the above code is equivalent to:"

const flagIcon = new FlagIcon()
flagIcon.country = "jp"
document.body.appendChild(flagIcon)

So, the need for a non-callable constructor is there! For the moment I just put the super call into a try-catcher:

constructor() {
  try {super();} catch(e) {}
  ...
}

@0815fox
Copy link

0815fox commented Jul 11, 2016

Okay, for others, who struggle with this as well... The MDN source seems to reflect the current way it is implemented by webcomponents.js and chrome is, that document.registerElement returns a constructor, which you then can use to call:

class FooElement extends HTMLElement {
  ...
}
const FooElementCtor = document.registerElement('element-name',FooElement);
const FooInstance = new FooElementCtor();
document.querySelector('body').appendChild(FooInstance);

Just in case anyone else struggles with this... Using the constructor of FooElement directly will throw an Invalid Constructor-exception. As I generate the DOM completely programatically, I do not use the element-name as tag at all, but it probably has to be a valid tag name containing at least one dash - - character. The example as shown above works for me.
However, the W3C draft as of 24 June 2016 assumes, that calling the constructor directly may be supported in future, or it is just an error in the spec.

@ZanderBrown
Copy link

Reading the "What's New" for 2.1 there seems to be some new feature for working with Custom Elements but i don't seem to be able to locate any example of it's usage

@saschanaz
Copy link
Contributor

saschanaz commented Apr 3, 2017

i think they should all be classes... eventually.

Are there any blocking problems not to do this now? microsoft/TypeScript-DOM-lib-generator#222

#2957 is still open but seems not really a blocking problem. In #563 (comment):

The current workaround of interface + prototype.method = ... does enable the generated-code scenario just as well as partial class would.

@saschanaz
Copy link
Contributor

saschanaz commented Apr 24, 2017

From #15348 (comment)

I think we should add classes regardless. that is covered by #574

@mhegazy You mean you will accept a PR for this?

@mhegazy
Copy link
Contributor

mhegazy commented Apr 24, 2017

I think we would. my concerns would be back compat. so we will need to run the new chance on DT, and on our internal test suite to make sure there are no breaks we are not aware of. if all passes, do not see why we can not take the change.

@saschanaz
Copy link
Contributor

Hmm, DT will be a best place for a first PR then 😃

@mhegazy
Copy link
Contributor

mhegazy commented Apr 24, 2017

Hmm, DT will be a best place for a first PR then

Not sure what you mean? i meant make the change to https://github.com/Microsoft/TSJS-lib-generator, build a custom TS version with the new library, then compile all DT types with the custom TS drop. ideally you should not see any errors.

@saschanaz
Copy link
Contributor

I thought you would publish a new lib.d.ts on DefinitelyTyped and get user feedback. My misunderstanding.

@xt0rted
Copy link

xt0rted commented Sep 17, 2018

I'm trying to put together a definition for github/query-selector but I'm unable to use the generic type on the klass parameter because of how Element, HTMLElement, etc. are typed. For now I'm having to use any which isn't really ideal.

declare module '@github/query-selector' {
    type Queryable = Document | DocumentFragment | Element;

    export function query<T extends Element = Element>(context: Queryable, selectors: string, klass?: any): T;

    export function querySelectorAll<T extends Element = Element>(context: Queryable, selector: string, klass?: any): Array<T>;
}

Are Element etc. going to be changed to classes so scenarios like this work?

@freshgum-bubbles
Copy link

Not to necro, but is there something I'm missing here? This is a fairly standard interface by now, and not having typings for connectedCallback, attributeChangedCallback seems like an odd oversight (especially with the popularity of web components).

Everything else related to this seems to be typed, such as ShadowRoot & co.

I would have assumed that this would have been implemented via some sort of CustomHTMLElement-esque interface that types all the standard WC methods, like so:

interface CustomHTMLElement {
  connectedCallback?(): void;
  disconnectedCallback?(): void;
  adoptedCallback?(): void;
  attributeChangedCallback?(name: string, oldValue: string, newValue: string): void;
}

Is this a deliberate omission, or have the TS team just not got around to it yet?
Is it outside the scope of lib.dom.d.ts?

Re: DefinitelyTyped, I've searched the codebase and all I can find are
a bunch of re-implementations of the same interface as above.

If this is a separate issue, I'm happy to post it as such -- this is the closest related issue
that I could find here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: lib.d.ts The issue relates to the different libraries shipped with TypeScript In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests