1

I have the following example interface/class

export interface MyErrorI {
    something: boolean;
}

export class MyError extends Error implements MyErrorI {
    public something = false;

    constructor(message: string, key: keyof MyErrorI ) {
        super(message);

        this[key] = true; // this is the code that I want to reuse
    }
}

The concept is that the constructor sets the value of its properties based on its key param.

Now I want to extend it with another class:

interface MyNewErrorI extends MyErrorI {
    somethingElse: boolean;
}

export class MyNewError extends MyError implements MyNewErrorI {
    public somethingElse = false;

    constructor(message: string, key: keyof MyNewErrorI) {
        super(message, key);
    }
}

Now, the key param can have a property of the interface which extends MyErrorI. I don't want to copy/paste the code from the parent class' constructor to the child class' constructor, due to its complexity in my actual problem.

However, the code above can't work because the constructor of MyError expects a key of MyErrorI. However, the key somethingElse is a valid property of MyNewError.

So, the question is: Is it possible that the parent class' constructor accesses the key param, each time based on its child class that calls super?

I tried to do something like this, but it didn't work either:

export class MyError<T extends PaymentErrorI> extends Error implements MyErrorI {
    public something = false;

    constructor(message: string, key: keyof T) {
        super(message);

        this[key] = true;
    }
}

The actual constructor is the following:

constructor(message: string, key: keyof MyErrorI | Array<keyof MyErrorI>, data?: any) {
        super(message);

        if (key instanceof Array) {
            key.forEach((v) => {
                this[v] = true;
            });
        } else {
            this[key] = true;
        }

        this.payload = data;
}

So calling it using super(message, key, data) saves more lines of code, and makes it more maintainable.

Alternatively, could I use a function which has the same functionality? Something like

const setKeys = <T>(err: T, key: keyof T | Array<keyof T>)

which would provide the functionality needed. Like this, for err[key] = true I get Type 'true' is not assignable to type 'T[keyof T]'

Thanks in advance!

2
  • I don't see an obvious solution, but it hardly seems worth worrying about reusing a single line of code. Can you give a more realistic example of your scenario? Commented Oct 15, 2018 at 3:09
  • Thanks for your answer, I have updated the question to include a more realistic constructor and a potential alternative Commented Oct 15, 2018 at 5:26

1 Answer 1

1

A function separate from the constructor is probably the way to go, since the constructor can't override the expected this type or use the special this type to refer to the type of the subclass being constructed, and adding a type parameter just for the sake of the key-setting would be very clunky. The problem with your function signature is that you haven't ensured that the keys of T being passed in have boolean values, so assigning true to them might not be valid. The function definition should be something like this:

const setKeys = <K extends keyof any>(err: {[P in K]: boolean}, key: K | Array<K>) => {
    if (key instanceof Array) {
        key.forEach((v) => {
            err[v] = true;
        });
    } else {
        err[key] = true;
    }
};

Or you could make setKeys a method of MyError, since a method (unlike a constructor) can override the expected this type:

export class MyError extends Error implements MyErrorI {
    // ...
    setKeys<K extends keyof any>(this: { [P in K]: boolean }, key: K | Array<K>) { 
        if (key instanceof Array) {
            key.forEach((v) => {
                this[v] = true;
            });
        } else {
            this[key] = true;
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.