Typescript: Allow subclass constructors without super() call

Created on 29 Feb 2016  ·  21Comments  ·  Source: microsoft/TypeScript

TypeScript Version:

1.8.0

Code

Custom element classes often have constructors that just return document.createElement(), since HTMLElement doesn't have a callable constructor, but this generates an error in TypeScript:

class MyElement extends HTMLElement {
  constructor() {
    return document.createElement('my-element');
  }
}

In general, it is completely valid ES2015 to not call super(), as long as you don't access this.

Needs Proposal Suggestion

Most helpful comment

Apparently TS's super() check isn't too robust, so I just did this:

constructor() {
  if (1 < 0) { // edit, this used to be 1 < 0, @xLama is right :)
    super();
  }
  return document.createElement('my-element');
}

All 21 comments

Something similar was discussed in #5910

I know this is a very ugly solution but It works.

class MyElement extends HTMLElement {

    constructor() {
        try{
            super();
        }catch(e){
        }finally{
            return document.createElement('my-element');
        }
    }
}

Or without catch

class MyElement extends HTMLElement {

    constructor() {
        try{
            super();
        }finally{
            return document.createElement('my-element');
        }
    }
}
class MyElement extends HTMLElement {
    constructor(element : string) {
        try{
            super();
        }finally{
            return document.createElement(element);
        }
    }
}

alert(new MyElement("div"));

Apparently TS's super() check isn't too robust, so I just did this:

constructor() {
  if (1 < 0) { // edit, this used to be 1 < 0, @xLama is right :)
    super();
  }
  return document.createElement('my-element');
}

I think it dows not work because it call super constructor. 1 is always greater than 0. Maybe you wanted to write 1 < 0.

I think that code will beacome deprecated when unreachable code detection improves.

this does not work

class MyElement extends HTMLElement {
    constructor() {
        if (false){
          super();
        }
        return document.createElement();
    }
}

this it does

class MyElement extends HTMLElement {
    constructor() {
        if (0){
          super();
        }
        return document.createElement();
    }
}

Oops, I meant the other way! Editing...

@xLama there will always be some way to beat compile-time evaluation... it'll just get uglier over time.

What's the use of a class like this? Why not just have a factory function?

It seems crazy to have MyElement such that (new MyElement()) instanceof MyElement is false

(new MyElement()) instanceof MyElement will be true. document.createElement('my-element') will create an instance of MyElement.

The use, in this case, is that users want constructors, even before the built-in HTMLElement subclasses get callable constructors.

In other cases, a constructor could return a subtype, or return a cached instance.

That's not what I'm seeing in Firefox nightly -- maybe a bug on their side?

Firefox custom element support is behind a flag. You need to enable dom.webcomponents.enabled in about:config. Or try on Chrome.

Here's an example that works in Chrome: http://jsbin.com/xivutarobo/edit?html,console,output

With polyfills is also works on other browsers without flags.

Adding the code here, for convenience:

    class MyElement extends HTMLElement {
      constructor() {
        return document.createElement('my-element');
      }

      createdCallback() {
        console.log('created my-element');
      }
    }
    document.registerElement('my-element', MyElement);
    let el = new MyElement();
    console.log(el instanceof MyElement); // true

Is more info still needed, or was this enough?

I think this is enough.

For the vast majority of ES6 classes, not calling super is a big runtime error. We'd need to figure out how to still give an error in those cases while still allowing this.

One big sign that superisn't necessary in the case of factory constructors is the presence of a return statement. Along with no access to this, it might be enough.

Otherwise, is there any existing warning suppression mechanism in TypeScript?

@justinfagnani If you are returning a value, not accessing this, and not calling super, why is it defined as a class. What exactly do you mean by 'factory constructors'? I know what they are in dart, but in JavaScript/TypeScript people usually speak in terms of factories _or_ constructors.

First, it's just valid JavaScript that can type check, so it seems like it should be supported by a superset of JavaScript. As for use cases, the custom element use case is my primary concern. HTMLElement doesn't have a callable constructor yet, so extending it requires not calling super().

As for the term "factory constructor", yes Dart explicitly has this concept, but JavaScript already supports constructors returning something other than this. I think such constructors can reasonably be called factory constructors, instead of "constructors that return something other than this".

Given that HTMLElement's constructor never actually returns, it should actually return never. I'm thinking that

  1. It should be allowed to extend something which has a construct signature of type never.
  2. If a construct signature of some value's type returns never, then a super call is not needed if extending from that value.
  3. If there is a super() call, it should be an error to access this before super (which is currently the case anyway).

The examples here, specifically regarding subclasses of HTMLElement are a bit out of date.
HTMLElement is now specified to return a value. See: https://html.spec.whatwg.org/multipage/dom.html#html-element-constructors in particular step 11.

https://github.com/Microsoft/TypeScript/issues/7574 is much, much more important to the Custom Elements use cases now.

Is there any way this check could be put behind a flag? Being able to disregard super is incredibly useful for evolutions.

In my case I want to evolve an event, so I have something like this:

class SuperEvent extends Event {
  public superHeroIdentity: string;

  constructor(event: Event) {
    super(); // really trying to get rid of this line
    (event as any).superHeroIdentity = "Megavent";
    return event;
  }
}

const boringEvent = new Event("doThing").
const superEvent = new SuperEvent(boringEvent);
console.log(superEvent); // Megavent

I know evolution's aren't a wide spread concept in the programming world just yet, but they're incredibly useful and one of the best things about javascript. All of this is valid javascript, so it would be really nice if typescript was capable of it as well. Also I recognize how this breaks strict typing, but I'm not really trying to compile wasm here.

Also, in this case

MyElement such that (new MyElement()) instanceof MyElement is [true]

Was this page helpful?
0 / 5 - 0 ratings