1

I have a family of three classes:

abstract class Form {
    protected async submit({ url: string, data: any }): Promise<void> {
        // submit data from form
    }
}

abstract class BookForm extends Form {
    public name?: string;

    public abstract async submit(): Promise<void>
}

class UpdateBookForm extends BookForm {
    private id: string;

    public async submit(): Promise<void> {
        // Abstract method 'submit' in class 'BookForm' cannot be accessed via super expression.
        return super.submit({
            url: '/book/update',
            data: {
                id: this.id,
                name: this.name,
            }
        });
    }

    constructor(bookId: string) {
        super()
        this.id = bookId;
    }
}

I'm getting an error when trying to access the super.submit() method from UpdateBookForm.

Abstract method 'submit' in class 'BookForm' cannot be accessed via super expression.

What I'm trying to achieve is that BookForm is aware of its derivatives containing method submit(), without any implementation in BookForm.

Any suggestions on how to accomplish this? Maybe I can access the grandparent directly (super.super?) from UpdateBookForm?

3 Answers 3

2

Remove the abstract method from BookForm, making it:

abstract class BookForm extends Form {
    public name?: string;
}

Just making BookForm abstract is sufficient to prevent it being used.

You never want to override a method that exists (in Form) with one that doesn't exist (in BookForm).

In a comment you've outlined the motivation for making submit abstract by saying (I've changed "save" to "submit" in the below to match the question):

Ok, well say I have an instance of BookForm as in const bookForm = this.book ? new UpdateBookForm(this.book) : new CreateBookForm() and I attempt to save it bookForm.submit() it expects the submit method from Form which requires arguments. I would want to call bookForm.submit().

If BookForm and UpdateBookForm don't meaningfully have a submit that accepts arguments, then they break the rule that a subclass instance "is a" superclass instance because they aren't Forms. There are various ways to solve that. One would be to make submit protected and have BookForm add an abstract save or something. Then UpdateBookForm and CreateBookForm would implement save by calling submit.

But I'd also closely examine whether inheritance is the right call here. It's hard to say without more context.


Side note: BookForm should probably have a constructor that initializes name. Introducing a field without a means of initialzing it at the same level of the hierarchy seems off.

Sign up to request clarification or add additional context in comments.

5 Comments

Ok, well say I have an instance of BookForm as in const bookForm = this.book ? new UpdateBookForm(this.book) : new CreateBookForm() and I attempt to save it bookForm.save() it expects the save method from Form which requires arguments. I would want to call bookForm.save().
@nomadoda - By save I assume you mean submit (the name used in your question). If BookForm and UpdateBookForm don't meaningfully have a submit that accepts arguments, then they break the rule that a subclass instance "is a" superclass instance because they aren't Forms. There are various ways to solve that. One would be to make submit private and have BookForm add an abstract save (not submit). Then UpdateBookForm and CreateBookForm would implement save by calling submit. But I'd also closely examine whether inheritance is the right call here.
Thank you so much! I expanded my solution in a separate answer. It seems to make sense in my situation, although I worry I'm digging myself a hole. I'm still trying to learn OOP, and your expertise is very valuable to me. Do you think this way could cause major issues down the line?
@nomadoda - Nope, should be fine, although again depending on the overall context (which obviously I don't have), the decision to use inheritance for this may or may not be ideal.
@nomadoda - I've folded the comments above into the answer.
0

You an call it directly using the grandparent class name, although you might consider redesigning your code.. it has a code smell.

abstract class Form {
    async submit({ url: string, data: any }): Promise<void> {
        // submit data from form
    }
}

abstract class BookForm extends Form {
    public name?: string;

    abstract async submit(): Promise<void>
}

class UpdateBookForm extends BookForm {
    private id: string;

    async submit(): Promise<void> {
        return Form.prototype.submit.call(this, {
            url: '/book/update',
            data: {
                id: this.id,
                name: this.name,
            }
        });
    }
}

2 Comments

Interesting. I'm evaluating some different strategies and, although off-topic, it would be very valuable for me if you could elaborate on the code smell.
@nomadoda I can't really put in into words, but it seems strange to have method that goes from non abstract to abstract in a derived class. I can't think of a way this could cause a runtime error, but it seems strange to me that is all. That being said, the code above should work, you have the gun to shoot your problem just be careful you don't shoot your foot :P
0

Thanks to T.J. Crowder's answer, I came to the conclusion that the Form submit({ url: string, data: any }) function's actually quite different from the subclass submit() functions, so it makes sense to separate them into two functions as such:

abstract class Form {
    protected async submit({ url: string, data: any }): Promise<void> {
        // submit data from form
    }
    public abstract async save(): Promise<void>
}

abstract class BookForm extends Form {
    public name?: string;

    public abstract async save(): Promise<void>
}

class UpdateBookForm extends BookForm {
    private id: string;

    public async save(): Promise<void> {
        return super.submit({
            url: '/book/update',
            data: {
                id: this.id,
                name: this.name,
            }
        });
    }

    constructor(bookId: string) {
        super()
        this.id = bookId;
    }
}

1 Comment

No need to redeclare save in BookForm. It inherits it from Form.

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.