Here is a simple example that demonstrates the problem:
- MyBaseClass is defined as an abstract class. Like other OOP languages, this means that the class cannot be instantiated directly.
- MySubClass is a concrete subclass of MyBaseClass that can be instantiated.
- New in TypeScript 2.0 is the ability to define properties as abstract, which I have done with string1 and string2. These properties must be set in the subclass, or the transpiler will generate an error.
- The parent class constructor sets the string3 property based on the values of string1 and string2 set in the subclass. Imagine that string3 is a property that will be used by other methods in the base class (not shown in the example code), so it is a valid design choice to set that property in the constructor.
- Finally, the last two lines of code instantiate the class and display string3.
As you can see, the subclass properties aren’t set until AFTER the base constructor is called. Ryan Cavanaugh from Microsoft explains:
This is the intended behavior.
The order of initialization is:
1. The base class initialized properties are initialized
2. The base class constructor runs
3. The derived class initialized properties are initialized
4. The derived class constructor runs
As an OOP veteran of other languages, I find this behavior unfortunate. By defining a class as abstract, you are in effect saying it is “incomplete“, and it will be completed by its concrete subclasses. These technical restrictions on property initialization and constructors get in the way, but there are things we can do to work around the problem.
Rather than setting properties in the subclass, you can pass values to the base class constructor.
This works, but stylistically, I don’t like it for an inheritance-based approach. I’d rather have the ability to simply set properties in the subclass, but call it personal preference. There is nothing wrong with this solution.
Constructor Hook Method
Here, I’ve added an initialize() hook method to the constructor that runs before the buildString3() method. This gives the subclass an opportunity to set properties the base class needs at the appropriate time. I’ve declared the initalize() method as abstract, so that it must be implemented in the subclass.
This also works, but it leaves much to be desired. Even though I have declared the initialize() method as abstract, nothing forces the string1 and string2 properties to be set. Notice that I had to remove the abstract keyword from those properties for this to transpile without error. In general, I like the idea of adding hook methods for subclasses to use, but they should be optional. The base class should not depend on them, nor should it be ambiguous about which properties need to be set.
As you may have gathered from the above, methods do not suffer from the same constructor timing issues as properties. The base class constructor called into the subclass initialize() method, and it functioned as expected. Likewise, using getter/setter syntax for properties is an option:
This is closer to the original vision. Having to use getter syntax is a little wordy for my taste, when all you want to do is return a simple value. You may not mind if you are used to this from other languages.
Move the Code
Finally, my favorite solution is to move the code out of the constructor, which is where the timing issue is. I moved the code into the string3 property with getter syntax. It won’t run until the property is accessed after the object has been constructed, so the timing issue is avoided. I also added a private _string3 property for improved performance, but of course, that is optional.
This solution is the closest the original code. I also like the idea of doing more in the base class, so you can do less in subclasses.
Your mileage may vary depending on your specific scenario, so choose the workaround that works best for you.