Do you want to request a feature or report a bug?
Bug (maybe)
What is the current behavior?
Pre save hook is not working correctly on latest ts-node version.
If the current behavior is a bug, please provide the steps to reproduce.
This is my schema:
export const UserSchema = new Schema({
email: { type: String, lowercase: true },
password: String,
});
UserSchema.pre('save', function (next) {
const user = this;
console.log(user.email);
next();
});
export const User = model<IUser>('User', UserSchema);
My IUser interface includes email
and password
too.
If I want to compile this script with ts-node
following TSError during compilation:
Property 'email' does not exist on type 'Document'.
What is the expected behavior?
this
should work as listed in the documentation.
Please mention your node.js, mongoose and MongoDB version.
node.js version: 8.11.3
mongoose version: 5.2.3
@types/mongoose: 5.2.0
ts-node: 7.0.0
typescript: 2.9.2
MongoDB version: 3.6.2
I have no practical experience with typescript so please excuse any obvious mistakes. Here is an example that I got to work:
NOTE that in usermodel.ts
my pre save function has the ts interface
export interface IUser {
email?: string;
firstName?: string;
lastName?: string;
}
import { Document, Schema, Model, model } from "mongoose";
import { IUser } from "./user";
export interface IUserModel extends IUser, Document {
fullName(): string;
}
export var UserSchema: Schema = new Schema({
email: String,
firstName: String,
lastName: String
});
UserSchema.pre("save", function<IUserModel>(next) {
const user = this;
console.log(user.email);
next();
});
UserSchema.methods.fullName = function(): string {
return this.firstName.trim() + " " + this.lastName.trim();
};
const User: Model<IUserModel> = model<IUserModel>("User", UserSchema);
export default User
import * as mongoose from 'mongoose';
const conn = mongoose.connection;
import User from './usermodel';
mongoose.connect('mongodb://localhost:27017/tsTest', { useNewUrlParser: true })
const user = new User({
email: '[email protected]',
firstName: 'Billy',
lastName: 'TheKid'
})
async function run() {
console.log(mongoose.version)
await conn.dropDatabase()
await user.save()
let found = await User.findOne()
console.log(found)
return conn.close()
}
run()
6725: ./node_modules/.bin/ts-node ./index.ts
5.2.3
[email protected]
{ _id: 5b4b4c03123f3a1d7de3436d,
email: '[email protected]',
firstName: 'Billy',
lastName: 'TheKid',
__v: 0 }
6725:
Thanks a lot.
Adding <IUser>
to my pre
function fixed it for me.
UserSchema.pre('save', function <IUser>(next) { // ... }
But also there are existing now some linting warnings.
[ts] 'IUser' is declared but its value is never read.
and
[tslint] Shadowed name: 'IUser' (no-shadowed-variable)
My research on what <IUser>
on a function will do resulted in the following.
https://www.typescriptlang.org/docs/handbook/generics.html
So I don't get it why this is fixed now. Is this an intended behavior?
from this page in the typescript docs you can pass the value of this
as the first parameter:
try this instead and see if that gets rid of the linter errors:
UserSchema.pre("save", function(this: IUser, next) {
const user = this;
console.log(user.email);
next();
});
my example with this change worked as well.
closing this as it isn't related to mongoose. If you want continue the conversation please feel free to join us on Gitter.im or Slack.
Nice job with this @lineus :+1:
For me, the issue turned out to be the context of this
was changing.
For example, this code runs fine:
UserSchema.pre("save", function(this: IUser, next: any): void {
const user = this;
// Generate the salt
bcrypt.genSalt(+config.saltWorkFactor, function(error: Error, salt): void {
if (error) return next(error);
let password = user.password;
// Hash the password
bcrypt.hash(password, salt, function(error, hash): void {
if (error) return next(error);
password = hash;
next();
});
});
});
But this code does not:
UserSchema.pre("save", function(this: IUser, next: any): void {
// Generate the salt
bcrypt.genSalt(+config.saltWorkFactor, function(error: Error, salt): void {
if (error) return next(error);
let password = this.password;
// Hash the password using the salt
bcrypt.hash(password, salt, function(error, hash): void {
if (error) return next(error);
password = hash;
next();
});
});
});
@devnoot you're right, you need to be careful to not lose function context when using callbacks. But why are you using callbacks when you're using typescript? I'm pretty baffled :confused:
Most helpful comment
from this page in the typescript docs you can pass the value of
this
as the first parameter:try this instead and see if that gets rid of the linter errors:
my example with this change worked as well.
closing this as it isn't related to mongoose. If you want continue the conversation please feel free to join us on Gitter.im or Slack.