Skip to content

Allow subclass constructors without super() call #7285

Description

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.

Activity

  1. nippur72 commented on Feb 29, 2016

    @nippur72

    Something similar was discussed in #5910

  2. xLama commented on Feb 29, 2016

    @xLama

    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"));
    
    
  3. justinfagnani commented on Feb 29, 2016

    @justinfagnani
    Author

    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');
    }
  4. xLama commented on Feb 29, 2016

    @xLama

    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();
        }
    }
    
    
  5. justinfagnani commented on Feb 29, 2016

    @justinfagnani
    Author

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

  6. justinfagnani commented on Feb 29, 2016

    @justinfagnani
    Author

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

  7. RyanCavanaugh commented on Feb 29, 2016

    @RyanCavanaugh
    Member

    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

  8. justinfagnani commented on Feb 29, 2016

    @justinfagnani
    Author

    (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.

  9. RyanCavanaugh commented on Feb 29, 2016

    @RyanCavanaugh
    Member

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

  10. justinfagnani commented on Feb 29, 2016

    @justinfagnani
    Author

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

  11. justinfagnani commented on Feb 29, 2016

    @justinfagnani
    Author

    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.

  12. justinfagnani commented on Feb 29, 2016

    @justinfagnani
    Author

    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
  13. justinfagnani commented on Mar 4, 2016

    @justinfagnani
    Author

    Is more info still needed, or was this enough?

  14. RyanCavanaugh commented on Mar 4, 2016

    @RyanCavanaugh
    Member

    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.

  15. justinfagnani commented on Mar 5, 2016

    @justinfagnani
    Author

    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?

  16. aluanhaddad commented on Mar 11, 2016

    @aluanhaddad
    Contributor

    Justin Fagnani (@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.

  17. justinfagnani commented on Mar 11, 2016

    @justinfagnani
    Author

    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".

  18. DanielRosenwasser commented on Jul 19, 2016

    @DanielRosenwasser
    Member

    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).
  19. justinfagnani commented on Jul 19, 2016

    @justinfagnani
    Author

    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.

  20. justinfagnani commented on Jul 19, 2016

    @justinfagnani
    Author

    #7574 is much, much more important to the Custom Elements use cases now.

  21. SephReed commented on Sep 15, 2018

    @SephReed

    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]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.SuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions