63

I'm trying to implement a method in a super class that should be available for use, but not changeable, in sub classes. Consider this:

export abstract class BaseClass {
    universalBehavior(): void {
        doStuff(); // Do some universal stuff the same way in all sub classes
        specializedBehavior(); // Delegate specialized stuff to sub classes
    }

    protected abstract specializedBehavior(): void;
}

My intention would be that any sub class of BaseClass would not only be free to omit implementation of universalBehavior(), but not even be allowed to provide an implementation. Is this not (yet) possible in TypeScript? Intellisense complains when I omit the implementation in my sub classes. The best I can seem to do is this:

export class SubClass extends BaseClass {
    universalBehavior(): void {
        super.universalBehavior();
    }

    specializedBehavior(): void {
        // sub class' implementation
    }
}

Obviously this is problematic because I have to ensure that no sub class ever implements universalBehavior() with anything other than a call to super.universalBehavior().

4
  • 1
    Here's the discussion on adding final to TypeScript: github.com/Microsoft/TypeScript/issues/8306 Commented Mar 15, 2017 at 15:59
  • @MikeMcCaughan - So I guess there is no keyword. Perhaps someone will post a workaround of some sort.
    – bubbleking
    Commented Mar 15, 2017 at 18:22
  • I've seen the use of a @readonly decorator, which will use Object.freeze on the object, but that's not the same thing (it disallows any modification to the class). Commented Mar 15, 2017 at 18:28
  • In JavaScript this problem usually solved by invoking prototype method directly on any subclass, instead of making use of prototype chain. One example that is often can be seen Object.hasOwnProperty.call(target, "some-property") to check if property comes from object itself and not from prototype chain. You can use the same trick and it won't even matter if subclass shadows this method or not. Commented Sep 5, 2019 at 4:15

3 Answers 3

39

No, at the time of this writing there is not. There is a proposal for such a keyword which is still being considered, but may or may not ever be implemented.

See:

0
34

Example of implementation hack of 'sealed method' as readonly property of type function which throws compiler error when attempting to override in extended class:

abstract class BaseClass {
    protected element: JQuery<HTMLElement>;
    constructor(element: JQuery<HTMLElement>) {
        this.element = element;
    }
    readonly public dispose = (): void => {
        this.element.remove();
    }
}

class MyClass extends BaseClass {
    constructor(element: JQuery<HTMLElement>) {
        super(element);
    }
    public dispose(): void { } // Compiler error: "Property 'dispose' in type 'MyClass' is not assignable to the same property in base type 'BaseClass'"
}

TypeScript 2.0 supports "final" classes through using of private constructor:

class A {
    private constructor(){}
}

class B extends A{} //Cannot extend a class 'A'. Class constructor is marked as private.ts(2675)
7
  • 4
    Nice catch! Not even sure this is a hack. Looks perfectly valid and expected to me. Commented Sep 4, 2019 at 11:44
  • 1
    Although, I fail to see the logic of throwing error here, since extending base prototype in JavaScript this way doesn't modify it at all. Commented Sep 5, 2019 at 4:03
  • 2
    @weaknespase I think how the compiler converts the Typescript to Javascript has changed, because putting the above into the Typescript playground, the error now is "Class 'BaseClass' defines instance member property 'dispose', but extended class 'MyClass' defines it as instance member function.". This means it's really easy to get around - not a reliable workaround for a 'sealed method'.
    – Asher G.
    Commented Sep 5, 2019 at 18:49
  • 10
    This is not a valid solution. If the child class also uses a lambda property TS will not complain. typescriptlang.org/play/…
    – lorefnon
    Commented Jan 5, 2020 at 13:12
  • 7
    but then you can't create a new A()...so it's not really the same as final classes in other languages
    – Andy
    Commented Oct 21, 2020 at 17:58
-2
// I use this workaround:

export default class CreateHandler extends BaseHandler {
    // final prop used as method
    public readonly create = (blueprint: Blueprint): Response<Record> => {
        return blueprint.create();
    };

    // normal method
    public store(blueprint: Blueprint): Response<Record> {
        return this.response(blueprint.create());
    }
}
1
  • 3
    Please include an explanation of the code when posting an answer.
    – Brian Lee
    Commented Apr 10, 2020 at 0:58

Not the answer you're looking for? Browse other questions tagged or ask your own question.