I have a Base
class with a BaseParams
constructor parameter, and want to extend a Derived
class from Base
, that takes an ExtendedParams
constructor parameter, and passes it to super()
, along with some defaults. The problem is that I can't seem to find the right combination of access modifiers and types for the Derived
constructor parameter.
Here's what I have so far:
interface BaseParams {
first: string;
}
interface ExtendedParams extends BaseParams {
last?: string;
}
class Base {
constructor(protected params: BaseParams) {}
inspect(): void {
console.log(this.params);
}
}
class Derived extends Base {
constructor(??? params ???) {
super({ first: 'John', last: 'default', ...params }); // override default params with specified ones
this.params.last; // #1 - this should not generate any compile errors
this.params.whatever = 'Wrong'; // #2 - TS should flag this as not existing on type ExtendedParams
}
}
const d = new Derived({ first: 'Mike' });
d.inspect(); // #3 - should output { first: 'Mike', last: 'default', whatever: 'Wrong' }
I tried declaring the Derived
params
as protected
. That solves #1 and #2.
class Derived extends Base {
constructor(protected params: ExtendedParams) {
super({ first: 'John', last: 'default', ...params });
this.params.last; // #1 - ok, no error
this.params.whatever = 'Wrong'; // #2 - ok, flagged: `whatever` doesn't exist on type ExtendedParams
}
}
The problem is that the last: 'default'
assignment doesn't take place because protected
generates a this.params = params
assignment that overwrites the params
value passed to super()
, so d.inspect()
outputs only { first: 'Mike', whatever: 'Wrong' }
.
I tried omitting the access modifier for params
. This leads to the correct output { first: 'Mike', last: 'default', whatever: 'Wrong' }
, but also to this.params.last
being flagged as an error, does not exist on type 'BaseParams'
.
class Derived extends Base {
constructor(params: ExtendedParams) {
super({ first: 'John', last: 'default', ...params });
this.params.last; // #1 - wrong ly flagged
this.params.whatever = 'Wrong'; // #2 - ok, flagged
}
}
Is there some TypeScript magic to inform the compiler that in the Derived
class, this.params
is of ExtendedParams
type, and the defaults get passed to the super()
call and this.params
is not overwritten afterwards?
But from Derived
you access Base.params
which is clearly typed as BaseParams
.
If you want the params
property to change type in derived classes, you'd need a generic base type:
// <P = BaseParams> allows to instantiate Base with BaseParams as default type.
class Base<P = BaseParams> {
constructor(protected params: P) {}
/* ... */
}
class Derived extends Base<ExtendedParams> {
constructor(params: ExtendedParams) {
super({ first: 'John', last: 'default', ...params }); // override default params with specified ones
this.params.last; // OK
this.params.whatever = 'Wrong'; // Wrong
}
}