Reputation: 35
I'm attempting to return an instance of a class based on key, but I can't access class custom methods.
Typescript: 3.8.3
Here's the code:
abstract class BaseProvider {
foo!: string
}
class ProviderA extends BaseProvider {
public testA(): string {
return this.foo + 'a'
}
}
class ProviderB extends BaseProvider {
public testB(): string {
return this.foo + 'b'
}
}
type Provider = ProviderA | ProviderB
const providers = {
a: ProviderA,
b: ProviderB,
}
function useProvider<T extends keyof typeof providers>(key: T): Provider {
const defaultProvider = providers.a
const providerByKey = providers[key]
if (!providerByKey) {
console.warn(`Provider "${key}" doesn't exists.`)
return new defaultProvider()
}
return new providerByKey()
}
const providerA = useProvider('a')
/**
* Property 'testA' does not exist on type 'Provider'.
* Property 'testA' does not exist on type 'ProviderB'.
*/
providerA.testA()
const providerB = useProvider('b')
/**
* Property 'testB' does not exist on type 'Provider'.
* Property 'testB' does not exist on type 'ProviderA'.
*/
providerB.testB()
I expect to:
useProvider('a')
to return new ProviderA()
and have access to a method testA
.
useProvider('b')
to return new ProviderB()
and have access to a method testB
.
I found a "solution", to return a class instead of instance, but that's not what I need:
function useProvider<T extends keyof typeof providers>(key: T): typeof providers[T] {
return providers[key]
}
const providerA = new (useProvider('a'))()
providerA.testA()
Upvotes: 1
Views: 35
Reputation: 250036
You are pretty close to a solution, you just need to specify the relation between the return type and T
. The return type will be the instance type, of the class located in the providers
option at key T
, so InstanceType<typeof providers[T]>
The only problem is that InstanceType<typeof providers[T]>
is not clear to the compiler is the same as what is returned by new providerByKey()
, so you will either have to use a type assertion or move the specialized return type to a dedicated public signature while keeping your more permissive signature for the implementation signature as I have done below. Note that while this approach doe snot use type assertions it does mean you are on your own with regard to ensuring that what you return is actually in agreement with the more complex public signature)
function useProvider<T extends keyof typeof providers>(key: T): InstanceType<typeof providers[T]>
function useProvider<T extends keyof typeof providers>(key: T): Provider {
const defaultProvider = providers.a
const providerByKey = providers[key]
if (!providerByKey) {
console.warn(`Provider "${key}" doesn't exists.`)
return new defaultProvider()
}
return new providerByKey()
}
Upvotes: 2