Docs :: Add guidance on using loadClass with Typescript
Prerequisites
- [X] I have written a descriptive issue title
- [X] I have searched existing issues to ensure the feature has not already been requested
🚀 Feature Proposal
I'm trying to port a JS project that uses loadClass to TS (it's doing my head in trying to get types working completely ..). The documentation on TS is good enough, except doesn't mention anything about loadClass. A basic example showing how to use it with Mongoose's typings, or alternatively, a statement recommending against its use, and briefly why would be helpful.
Aside: the fundamental class structure implied by loadClass seems to be at odds with Mongoose's actual class structure. Maybe this wasn't a problem in JS work, but it's definitely, at the least, a bit confusing in TS world. Maybe loadClass should be deprecated.
Motivation
Help users trying to decide whether to try shoehorn loadClass into Mongoose typings or not.
Example
No response
as a note for those wanting information now instead of waiting for the documentation:
from what i can tell, loadClass currently does not affect the typescript type, so you would have to use the class like a interface and & it with the actual property interface
example:
class MyClass {
myMethod() { return 42; }
static myStatic() { return 42; }
get myVirtual() { return 42; }
}
const schema = new mongoose.Schema({ property1: String });
schema.loadClass(MyClass);
interface MySchema {
property1: string;
}
type MyCombined = MySchema & MyClass;
type MyCombinedModel = mongoose.Model<MyCombined> & typeof MyClass;
type MyCombinedDocument = mongoose.Document & MyCombined;
const model = mongoose.model<MyCombinedDocument, MyCombinedModel>('MyClass', schema);
model.myStatic();
const doc = new model();
doc.myVirtual;
doc.myMethod();
@hasezoey thanks.
Just to be clear, as far as I understand, there are "lean" caveats to above. Example: doc.toObject().myMethod() will compile and result in runtime error.
Main thing I wanted to do was type MyClass. The problem is "methods" and "statics" both have different base classes in Mongoose / Mongoose type system. So the only way I've found to make it work is split the class in two: MyStatics, MyMethods, and call loadClass twice. All the methods in MyStatics have to be static for no reason which is a bit hacky, so maybe there should be loadMethods(), loadStatics()` to be consistent with how Mongoose actually works.
Main thing I wanted to do was type MyClass. The problem is "methods" and "statics" both have different base classes in Mongoose / Mongoose type system. So the only way I've found to make it work is split the class in two: MyStatics, MyMethods, and call loadClass twice. All the methods in MyStatics have to be static for no reason which is a bit hacky, so maybe there should be loadMethods(), loadStatics()` to be consistent with how Mongoose actually works.
i am not sure what you mean with this, mongoose and typescript (and mongoose types) both correctly handle statics and instance methods just fine
in typescript you can access instance properties & methods via just MyClass and statics and static properties with typeof MyClass
just note that mongoose does not support defining properties in classes, only methods (both instance and static) and ES6 getter / setter
if you mean that the model type does not have the statics of your class, then you just need to do as shown in the example: type MyCombinedModel = mongoose.Model<MyCombined> & typeof MyClass;
I just mean typing of this within methods MyClass. What I did was:
class MyStatics extends Model { ... }
class MyMethods extends Document { ... }
...
schema.loadClass(MyStatics}
schema.loadClass(MyMethods)
Even this is limited because this is still missing the MySchema typings.
... and statics and static properties with
typeof MyClass
Ah yes, that's a useful tip to remember. Thank.
UPDATE: Just realised setting Document as base class of loadClass class results in errors (below). There is a specific guard (/hack) implemented to prevent loadClass copying methods from Model if it hits it, but for some reason the hack wasn't implemented for Document.
(node:6191) [MONGOOSE] Warning: mongoose: the method name "emit" is used by mongoose internally, overwriting it may cause bugs. If you're sure you know what you're doing, you can suppress this error by using `schema.method('emit', fn, { suppressWarning: true })`.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:6191) [MONGOOSE] Warning: mongoose: the method name "listeners" is used by mongoose internally, overwriting it may cause bugs. If you're sure you know what you're doing, you can suppress this error by using `schema.method('listeners', fn, { suppressWarning: true })`.
(node:6191) [MONGOOSE] Warning: mongoose: the method name "removeListener" is used by mongoose internally, overwriting it may cause bugs. If you're sure you know what you're doing, you can suppress this error by using `schema.method('removeListener', fn, { suppressWarning: true })`.
(node:6191) [MONGOOSE] Warning: mongoose: the method name "init" is used by mongoose internally, overwriting it may cause bugs. If you're sure you know what you're doing, you can suppress this error by using `schema.method('init', fn, { suppressWarning: true })`.
(node:6191) [MONGOOSE] Warning: mongoose: the method name "get" is used by mongoose internally, overwriting it may cause bugs. If you're sure you know what you're doing, you can suppress this error by using `schema.method('get', fn, { suppressWarning: true })`.
(node:6191) [MONGOOSE] Warning: mongoose: the method name "isModified" is used by mongoose internally, overwriting it may cause bugs. If you're sure you know what you're doing, you can suppress this error by using `schema.method('isModified', fn, { suppressWarning: true })`.
...
I just mean typing of this within methods MyClass. What I did was:
i am not quite sure what you are asking; are you maybe asking how to set the correct this type for methods in the class?
when yes, here is a example, just note that mongoose / typescript cannot do this automatically from what i know:
class MyClass {
myMethod(this: MyCombinedDocument) { return 42; }
static myStatic(this: MyCombinedModel) { return 42; }
get myVirtual(this: MyCombinedDocument) { return 42; }
}
i am not quite sure what you are asking; are you maybe asking how to set the correct this type for methods in the class?
Yes exactly. I'm aware you can set the type of this in the method signature, but it's a bit of a PITA when you have a class with 20 or so methods.
but it's a bit of a PITA when you have a class with 20 or so methods.
that is true, but like i had said in my last comment, i dont think typescript supports redefining the this for the whole class, it (currently) needs to be done on a per-method basis (basically in every method)
i dont think typescript supports redefining the this
I doesn't, except via inheritance.
@hasezoey – this parameters do not appear to work on getters/setters.
'get' and 'set' accessors cannot declare 'this' parameters.ts(2784)
@dandean thanks for pointing this out, i guess i didnt test get & set types specifically (or it may have worked in a previous typescript version, i dont recall)
related: https://github.com/microsoft/TypeScript/issues/52923