Reputation: 20467
Consider this code:
export default class OptimizelyFeatureFlags implements FeatureFlags {
private body: string;
constructor(
protected callEndpoint: CacheableEntity,
protected baseUrl: string,
protected envName: string,
protected featureId: string
) {}
/**
* Calls the Optimizely endpoint to get the info
*/
public async fetch(): Promise<void> {
// Determine if this is a CallEndpoint (or child thereof)
if (this.callEndpoint['hasAuthorisationToken'] != undefined) {
if (!this.callEndpoint.hasAuthorisationToken()) {
throw Error(
'This endpoint requires an auth token to work'
);
}
}
this.body = await this.callEndpoint.getResource(this.baseUrl + `/v2/features/${this.featureId}`);
}
...
}
Now CacheableEntity
is an interface, which requires only getResource()
to match the contract. The method hasAuthorisationToken
is not defined within it.
Now, if a class has a hasAuthorisationToken()
method, I know it is a CallEndpoint
, which will implement CacheableEntity
. This is a type test - from my research I understand that TypeScript does not offer run-time type tests, since at run-time it is merely JavaScript.
However, this won't compile. In a Jest test, I get:
● Test suite failed to run
src/service/feature-flags/OptimizelyFeatureFlags.ts:31:36 - error TS2339: Property 'hasAuthorisationToken' does not exist on type 'CacheableEntity'.
31 if (!this.callEndpoint.hasAuthorisationToken()) {
~~~~~~~~~~~~~~~~~~~~~
I guess there are several ways around this - maybe I can cast the object from CacheableEntity to CallEndpoint? I want to tell the transpiler that I know what I am doing.
Alternatively I could do something like this:
if (this.callEndpoint['hasAuthorisationToken'] != undefined) {
const hasAuth = this.callEndpoint['hasAuthorisationToken']();
...
}
However, I wonder if that is a bit messy. What is the best way around this?
Upvotes: 1
Views: 1324
Reputation: 1061
You can use a user defined type guard: https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards
Upvotes: 2
Reputation:
Yeah your first option is the best one. Most readable.
if (this.callEndpoint['hasAuthorisationToken'] != undefined) {
const callEndpoint = this.callEndpoint as CallEndpoint;
const hasAuth = callEndpoint.hasAuthorisationToken();
...
}
Not sure on the != undefined
bit. can you do this.callEndpoint instanceof CallEndpoint
instead? not sure how you're instantiating these things, but if you can use instanceof
, typescript will accept the call to the method on this.callEndpoint
without the cast. Like so:
if (this.callEndpoint instanceof CallEndpoint) {
const hasAuth = this.callEndpoint.hasAuthorisationToken();
...
}
Upvotes: 2