Skip to content

super() with an options object (possibly a FAQ) #7644

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

Closed
elibarzilay opened this issue Mar 22, 2016 · 6 comments
Closed

super() with an options object (possibly a FAQ) #7644

elibarzilay opened this issue Mar 22, 2016 · 6 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@elibarzilay
Copy link
Contributor

(Apologies if this is known, but I search a bunch of places and didn't find anything.)

Is there a good way to deal with a constructor of a derived class that is getting an options kind of value that needs to be merged with a defaults value? I started with something like this:

export class Foo extends Bar {
    ...
    private options: any = {};
    ...
    constructor(x: Something, y: SomethingElse, options: any = {}) {
        this.options = $.extend({}, defaults, options);
        super(this.options.blah);
        ...
    }
}

This failed and I eventually resorted to:

    constructor(x: Something, y: SomethingElse, options: any = {}) {
        super(options.blah || defaults.blah);
        this.options = $.extend({}, defaults, options);
        ...
    }

which is duplicating functionality (and sloppy).

Hopefully I'm not the only one who ran into this problem and there's good way to do that?

@Arnavion
Copy link
Contributor

The assignment to this.options before calling super() is illegal per ES6 semantics, so that can't be done anyway.

You could assign to a temporary (eg back to options), do the super() call, then assign options to this.options

constructor(x: Something, y: SomethingElse, options: any = {}) {
    options = $.extend({}, defaults, options);
    super(options.blah);
    this.options = options;
}

... but that still hits the error that the super() call needs to be first.

I've worked around a similar issue (needed to swap derived class constructor parameters before calling base class constructor to maintain backward compatibility) by abusing an IIFE as the super() parameter.

constructor(x: Something, y: SomethingElse, options: any = {}) {
    super((() => {
        options = $.extend({}, defaults, options);
        return options.blah;
    })());
    this.options = options;
}

Edit: Since you're always assigning to this.options in the constructor, you can remove the initialiazer for the class member options. Then the first alternative I gave will also compile.

@elibarzilay
Copy link
Contributor Author

The problem is that all of these are ugly hacks, and it looks very strange that the usual facility for keyword arguments thing cannot work in such constructors.

BTW, I couldn't get your hack to work, perhaps the TS version I'm using is too old. But if we're talking about such workarounds, then here's another ugly hack that works around it (based on an old Lisp hack of abusing optional arguments similarly):

constructor(x: Something, y: SomethingElse, _options: any = {},
            options: any = $.extend({}, Constants, _options)) {
    super(options.blah);
    this.options = options;
    ...
}

In any case, I find it surprising that there is no proper way to do this, given that it's a common enough JS pattern that it's been embraced as the proper way to do keyword arguments.

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Mar 23, 2016
@RyanCavanaugh
Copy link
Member

Not 100% sure what the best approach is, but since this is really an ES6 thing (must invoke super before using this) you might find some good answers on Stack Overflow by just posting under the [javascript] tag

@elibarzilay
Copy link
Contributor Author

@RyanCavanaugh, so I looked at it more, and I still think that there's a specific TS issue here. I can understand where the ES6 restriction is coming from, but trying it via TS still seem weird.

I dug some more, and it looks like usually initialized properties are handled after the super() call, except when there is some expression before the super, and even when that expression doesn't try to access this. Here's an example that shows this:

class Bar { private x; constructor(x: any) { this.x = x; }}

class Foo extends Bar {
    private mu = 9999;
    private options;
    constructor(x: string, y: number, options: any = {}) {
        // options = [123, options];
        super(options.blah);
        this.options = options;
    }
}

This works fine, and the output has this.mu = 9999; after the super call. Now uncomment the options setting line, and for some reason the mu initialization moves up to before the the super call. Is that intentional?

As a side note, I also think that it'll be good to add it to your FAQ since people will probably do what I did, and look for the error message. So a quick entry saying that it's an ES6 issue would be better than nothing...

@Arnavion
Copy link
Contributor

@elibarzilay I did say

Since you're always assigning to this.options in the constructor, you can remove the initialiazer for the class member options. Then the first alternative I gave will also compile.

@elibarzilay
Copy link
Contributor Author

@Arnavion, see the example in my reply to @RyanCavanaugh above: the problem is that if there is any initialized member TS does the assignment before the super call. In my case it's obviously ok to drop the initialization of options, but there are other members that are initialized.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

3 participants