With the following (very contrived) example where:
If the object has a certain value for an attribute, then another (optional) attribute must be defined. I want to catch errors in the constructor so that issues are immediately caught by the user.
Later in a method, I want to rely on the type checking that we performed earlier. Is there a way to "cascade" this check downwards, or leverage the checking we do in the constructor elsewhere in the class? Is there, generally, a more elegant way to structure this type of flow?
interface ExampleOptions {
someType: "basic" | "needsExtra"
extra?: string
}
class Example {
readonly someType: "basic" | "needsExtra";
readonly extra?: string;
constructor(opts: ExampleOptions){
this.someType = opts.someType
this.extra = opts.extra
// handle type checking here to raise error immediately
if (this.someType === "needsExtra" && !this.extra) {
throw new Error("gotta define extra when using type 'needsExtra'")
}
}
doSomething() {
return this.someType === "basic" ?
Example.helper(this.someType) : Example.helperExtra(this.someType, this.extra) // this throws
}
private static helper(someType: string): string {
return someType
}
private static helperExtra(someType: string, extra: string) {
return `${someType}${extra}`
}
}
The doSomething() method raises an exception:
Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
Type 'undefined' is not assignable to type 'string'.(2345)
even though we know from the constructor that this value will be initialized. I could always just add another check in the method, but that would be redundant. And I believe using the ! (non-null assertion) operator is frowned upon.
doSomething()tells me that you squeezed two classes in a single one. The constructor shows the same thing but it also suffers from using the classes as containers for properties, not as real OOP.