Access child properties from parent constructor
I like what Jeff and Duncan already have proposed quite a lot. Let me show one more option, though.
abstract class Animal {
constructor(public name: string) {
console.log(this.name);
}
}
class Snake extends Animal { }
let someSnake = new Snake("Snakeeeey!");
Notice a few things here.
- The
Animal
class seems to be indeed an abstract concept. You have an option of either marking it asabstract
or defining it as aninterface
, but the latter will not let you have any implementation (including constructor). - TypeScript allows us to define fields which are being initialized from constructor. In case of the public property we simply write
constructor(public name: string)
. -
The
Snake
class does not explicitly define a constructor. Nevertheless, when creating a snake object, the consumer has to provide thename
argument that is required byAnimal
class.In Jeff's code the Snake class has a default value for the
name
which is achieved byconstructor(name: string = 'BeatSSS') { super(name); }
. I don't know your specific use case. If there's no any meaningful default name for the snake, I would avoid declaring such constructor altogether. -
Duncan correctly explains that calling
super()
is not inherently wrong. Moreover, it is necessary.When an object of type
Snake
is being created by the means of its constructor, the parent (Animal
's ) constructor has to complete first. Therefore, there is no way forAnimal
constructor to "see" the effects of field initialization that happens in the child (Snake
's) constructor. That is impossible due to the order of constructor invocations.Thus, the only option for the child type to pass the information for parent's constructor is to call
super(...)
directly.
super()
call on childconstruct()
solve the problem, but I need do it on every child. Not very elegant solution.
Basically not defining a child constructor altogether is the answer. If your children class have to have the constructors they will force you to invoke super()
as the very first instruction.
using a timeout on parent construct logs the correct value, but is not possible use the instance of class immediately (beacouse timeout is not executed yet).
This is a very bad option. First, it's generally not recommended to have any asynchronous code invocations in constructor.
Second, if you still do a setTimeout(() => this.fixMyState(), 0)
in Animal
constructor it results in a bad effect: immediately after object construction, the object is in a bad state. What if some code uses that object immediately? The () => this.fixMyState(), 0
will not even have a chance to run for repairing the object. It is a rule of thumb to have an object in a good state immediately after its construction. Otherwise, the constructor should throw
an error so that no code can attempt to operate on the object in a bad state.
A hacky way to get the child value in the parent constructor would be to use setTimeout()
without delay
attribute.
Using yout example, the code would be as follows:
class Animal {
public name: string;
constructor() {
setTimeout(() => console.log(this.name));
}
}
class Snake extends Animal {
public name = 'BeatSSS';
}
let someSnake = new Snake();
Why does it work?
Because the function passed to setTimeout
will run after the current call stack is cleared. At this moment, the assignation of the child property value will be done, so you can access this value from the parent.
But be careful!
The value will remain undefined until the current call stack is cleared. This means that if, for example, you use the value to initialize the object in the parent, it won't be correctly initialized until the call stack ends.
The following example illustrates the issue:
class Animal {
public name: string;
public name_copy: string;
constructor() {
setTimeout(() => {
this.name_copy = name;
console.log('assigning name_copy --->', this.name_copy);
});
}
}
class Snake extends Animal {
public name = 'BeatSSS';
}
let someSnake = new Snake();
console.log('child name_copy --->', someSnake.name_copy);
This script would print out:
child name_copy ---> **undefined**
assigning name_copy ---> **BeatSSS**
If you need this on every child, you should pass the name argument into the constructor of the parent class.