Consider the following scenario where a base class is defined:
class BaseComponent<T> {
constructor(protected selectors: T) {}
}
LoginComponent.ts
const ADMIN_SELECTORS = {
inputUserName: "foo",
buttonLogin: "bar",
textError: "baz",
};
class LoginComponent<T> extends BaseComponent<typeof ADMIN_SELECTORS> {
constructor(protected selectors: typeof ADMIN_SELECTORS) {
super(selectors);
}
get loginButton() {
return this.selectors.buttonLogin;
}
}
// EXAMPLE CALL
const login = new LoginComponent(ADMIN_SELECTORS);
The use of typeof ADMIN_SELECTORS
in the code above serves the purpose of informing TypeScript about the structure of this.selectors
. This enables autocomplete and type checking for this.selectors
.
get userInput() {
return this.selectors.inputUser; // Property 'inputUser' does not exist on type '{ inputUserName: string; buttonLogin: string; textError: string; }'
}
Excellent!
Now, the task at hand is to create a subclass called CustomerLoginComponent
.
This subclass should have a different value for inputUserName
and introduce a new unique property called textCustomerName
in its selectors object.
CustomerLoginComponent.ts
const CUSTOMER_SELECTORS = {
inputUserName: "abc",
textCustomerName: "xyz",
};
class CustomerLoginComponent extends LoginComponent<typeof CUSTOMER_SELECTORS> {
constructor(selectors) {
super(selectors);
}
get customerName() {
return this.selectors.textCustomerName;
}
}
// EXAMPLE CALL
const customerLogin = new CustomerLoginComponent(CUSTOMER_SELECTORS);
An error arises with the message
Property 'textCustomerName' does not exist...
in the code segment below:
get customerName() {
return this.selectors.textCustomerName;
}
To resolve this, a modification is made within LoginComponent
:
class LoginComponent<T> extends BaseComponent<typeof ADMIN_SELECTORS> {
constructor(protected selectors: typeof ADMIN_SELECTORS & T) { // Note ' & T'!
super(selectors);
}
get loginButton() {
return this.selectors.buttonLogin;
}
}
Subsequently, no errors are encountered when working with CustomerLoginComponent
.
However, defining the annotation for selectors
in CustomerLoginComponent
poses a challenge. The suggested approach by TypeScript appears as follows:
class CustomerLoginComponent extends LoginComponent<typeof CUSTOMER_SELECTORS> {
constructor(selectors: { inputUserName: string; buttonLogin: string; textError: string; } & { inputUserName: string; textCustomerName: string; }) {
super(selectors);
}
}
It's impractical to hardcode the object structure due to its flexible nature (hence the use of typeof
). Moreover, the object resides in a separate file.
The main question remains: can CustomerLoginComponent
be aware that the type of selectors
comprises two merged objects - one from the parent class (the type of which should be somehow passed) and typeof CUSTOMER_SELECTORS
?
Exploring the infer
keyword has been attempted, but it may not serve the intended purpose...
Furthermore, uncertainties arise in regards to:
class LoginComponent<T> extends BaseComponent<typeof ADMIN_SELECTORS> {
constructor(protected selectors: typeof ADMIN_SELECTORS & T) {
}
}
There seems to be an issue in my base class definition:
class BaseComponent<T> {
constructor(protected selectors: T) {}
}
Note that an explicit annotation of the selectors' type as : typeof ADMIN_SELECTORS
in LoginComponent
is necessary for proper functioning.
In summary, the example highlighted reveals certain design flaws, thus any assistance in rectifying them would be highly appreciated.
Alternatively, guidance on annotating the type of selectors argument in CustomerLoginComponent
is sought.