Reputation: 7447
I've got a QueryBuilder
and would like to present all its methods as static member on the Model
class.
Apparently, It's valid Javascript code. However, how could I let typescript understand the existence of those static methods on Model
?
function queryable (target: any) {
for (const prop in target.prototype.builder) {
if (prop === 'constructor' || target.prototype.hasOwnProperty(prop)) {
continue;
}
target[prop] = (...args: any[]): any => {
return target.prototype.builder[prop](...args);
};
}
}
class QueryBuilder {
where(prop: string, value: string) {
console.log(prop, value);
console.log("I've presented on model class as static method.");
}
// ...
}
@queryable
class Model {
get builder () {
return new QueryBuilder();
}
// ...
}
// calling static where
Model.where('color', 'red');
Property 'where' does not exist on type 'typeof Model'.
See in Typescript Playground
Upvotes: 2
Views: 158
Reputation: 3506
The issue you should keep an eye on is TypeScript#4881 but as of currently (3.5.1) a decorator can't change the type of the class it is decorating.
That being said you can still define the decorator in the same fashion but call it explicitly rather than as a decorator, as many typescript projects using redux's popular connect
decorator have to do.
class _Model {
get builder () {
return new QueryBuilder();
}
// ...
}
const Model = queryable(_Model);
or if it is the main export of a module you can get away with one name:
class Model {
get builder () {
return new QueryBuilder();
}
// ...
}
export default queryable(Model);
Then the trick is to better annotate the decorator to teach typescript all the additions queryable
has made to it's target
:
function queryable<T extends new (...args: any[]) => {builder: any}>(target: T): InstanceType<T>['builder'] & (typeof target) {
for (const prop in target.prototype.builder) {
if (prop === 'constructor' || target.prototype.hasOwnProperty(prop)) {
continue;
}
(target as any)[prop] = (...args: any[]): any => {
return target.prototype.builder[prop](...args);
};
}
return target as any
}
Upvotes: 2
Reputation: 14015
Since Typescript is a strong typing language it always checks if type usage matches type interface. This checking takes place during transpilation, when Typescript has no idea you're going to add a new method. Because you add it in run-time. The only way to call such a method is casting to something general like any
:
(<any>Model).where('color', 'red');
It's like you're saying to Typescript that you're sure about method existence and transpiller shouldn't worry about it.
Upvotes: 0