Changing an object in the Mongoose pre-save hook

When working with a GeoJSON Polygon (or more precisely, a LinearRing), it is crucial that the last set of coordinates matches the first one:

[[0,0], [0,1], [1,1], [1,0]] // incorrect
[[0,0], [0,1], [1,1], [1,0], [0,0]] // correct

For my MongoDB instance using Mongoose, I want to implement a lenient validation approach (fixing the object instead of rejecting it) when saving a GeoJSON Polygon.

Here is my approach:

export type Coordinates = number[]

// model object
export class Polygon { 
  type: string;
  coordinates?: Coordinates[];

  constructor(coordinates?: Coordinates[]) {
    this.type = "Polygon";
    this.coordinates = coordinates;
  }
}

// schema object
export const PolygonSchema = { 
  type: String,
  coordinates: [[Number]],
};

// model-schema binding
const polygonSchema = new mongoose.Schema(PolygonSchema, { typeKey: '$type' }).loadClass(Polygon);

// pre-save hook
polygonSchema.pre('save', async (next, opts) => {
  // @ts-ignore
  const pol: Polygon = this; // Struggling with this assignment
  if (pol?.coordinates?.length > 1 && pol.coordinates[0] !== pol.coordinates[pol.coordinates.length - 1]) {
    pol.coordinates.push(pol.coordinates[0]);
  }
  next();
});

When debugging, tsc raises an error stating that this is always undefined, even though the Visual Studio debugger shows otherwise.

Is it possible to modify the object during the pre-save hook, or should I handle this task beforehand?

In case it matters, a Polygon will always be a subdocument in my MongoDB instance, representing the shape of another object.

Answer №1

If you use an arrow function instead of a traditional function with the pre('save', ...) function, you won't have access to the document. Arrow functions do not bind the this keyword, but instead use the parent scope.

According to Mozilla Developer Docs

Arrow functions do not have their own bindings for this, arguments, or super, so they should not be used as methods.

Make sure to change your arrow function to a traditional function definition.

Change this:

polygonSchema.pre('save', {document: true, query: true}, async (next, opts) => {...}

To this:

polygonSchema.pre('save', {document: true, query: true}, async function (next, opts) {...}

Answer №2

Surprisingly, I encountered an unexpected problem. It turns out that the this object cannot be accessed in anonymous (arrow) functions. This issue was pointed out in a video tutorial ( source: https://youtu.be/DZBGEVgL2eE?t=1495 ). Whether this limitation applies to JavaScript in general or is specific to Mongoose is uncertain, but I was able to resolve the problem using the following solution:

const polygonSchema = new mongoose.Schema(PolygonSchema, { typeKey: '$type' }).loadClass(Polygon);

async function preSave() {
  // @ts-ignore
  const pol: Polygon = this;
  if (pol?.coordinates?.length && pol.coordinates[0] !== pol.coordinates[pol.coordinates.length - 1]) {
    pol.coordinates.push(pol.coordinates[0]);
  }
}

polygonSchema.pre('save', preSave);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Ways to streamline redundant code by creating a higher order function that accepts different parameter types in TypeScript

Recently, I've been exploring the idea of refactoring this code into a higher order function using TypeScript to enhance its cleanliness and reusability. However, I'm facing quite a challenge in getting it to work seamlessly. import { DocumentDef ...

Concern regarding response and emotional reaction triggered by a third-party package

Before pushing my component to npm and installing it, I will include my vite.config.ts and package.json files in the component, along with the package.json file of the project that will be installing it: vite.config.ts: // vite.config.js import { resolve ...

How Vue3 enables components to share props

Having recently made the switch from Vue2 to Vue3, I find myself a bit perplexed about the best approach for sharing props among multiple components. My goal is to create input components that can share common props such as "type", "name", and so on. Previ ...

What is the proper method for specifying the path to my index.tsx file within a React application?

After using npx create-react-app my-app --template typescript to generate my React app, I decided to reorganize the files by moving them to /src/client and relocating my Express backend to /src/server. However, upon running the relocated React app, I encou ...

Modify every audio mixer for Windows

Currently working on developing software for Windows using typescript. Looking to modify the audio being played on Windows by utilizing the mixer for individual applications similar to the built-in Windows audio mixer. Came across a plugin called win-audi ...

What are the steps to set up NextJS 12.2 with SWC, Jest, Eslint, and Typescript for optimal configuration?

Having trouble resolving an error with Next/Babel in Jest files while using VSCode. Any suggestions on how to fix this? I am currently working with NextJS and SWC, and I have "extends": "next" set in my .eslintrc file. Error message: Parsing error - Can ...

[Protractor][Scroll] I need assistance with scrolling my webpage using a while loop. Could someone please help me troubleshoot the code?

When this function is called, it initiates scrolling and then pauses the browser for a 2-second period. scrollToElement(webElement: any) { browser.executeScript('window.scrollTo(0,400);').then(()=>{ console.log("sleepin ...

Using an Object as a parameter in a Typescript function

I am currently working on an Angular component that includes a function. Within this function, I need to pass an Object as a parameter and invoke the function with these parameters. It has been some time since I last worked with Angular, where "any" was ty ...

Exploring the depths of promises in mongoose

I am currently working with a promise in order to manipulate a database using mongoose. I am utilizing the mpromise library and taking teamMatch variable to update the Team document. However, the program seems to get stuck after the line where I update the ...

Inconsistency in pagination results in inaccurate record count

My API includes pagination, requiring a response in a specific format: "status": "Success", "message": "data found", "totalPages": 2, "currentPage": "1", "data": [ { ..... To display total pages and current page with 10 ...

Tips for efficiently loading data in a sequence using rxjs: within each sequence, there could be multiple calls made

const dates = [{start:'03/06/2020', end: '03/09/2020'}, {start:'03/12/2020', end: '03/03/2021'}, ...] const fetchData = service.get(page = 1, dates[0]) and service.get(page = 2, dates[0]) retrieves the necessary d ...

Tips for accurately defining prop types in next.js when utilizing typescript?

Here is the content of my index.tsx file: import type { NextPage } from "next"; type AppProps = { articles: { userId: number; id: number; title: string; body: string; }; }; con ...

Step-by-step guide on building a wrapper child component for a React navigator

When using the Tab.Navigator component, it is important to note that only the Tab.Screen component can be a direct child component. Is there a way in Typescript to convert or cast the Tab.Screen Type to the TabButton function? const App = () => { retur ...

The value of an Angular array seems to be disappearing after being copied to another array and then cleared

I have encountered an issue while working with arrays. I am initializing two arrays - one with some values and another as empty. However, when I assign the items from the first array to the second array and then clear the first array, it unexpectedly clear ...

Encountered an error while attempting to update an object: Unable to read property 'push' of undefined

Encountering an issue while attempting to update an object with additional information, receiving an error message stating 'property \'push\' of undefined'. /*Below is the object model in question:*/ export class Students { ...

Ways to sequentially execute API calls rather than concurrently

Update: Find the complete solution at the end of this answer. Consider the following code snippet: @Injectable() export class FileUploader { constructor(private http: Http) {} upload(url: string, file: File) { let fileReader: FileReader ...

Issue with Angular ngStyle toggle functionality not activating

I'm having an issue with toggling my navbar visibility on click of an image. It works the first time but not after that. Can anyone provide some assistance? Link to Code <img id="project-avatar" (click)="toggleNavbar()" width=20, height=20 style= ...

Discovering the number of items that have been filtered in isotope-layout using React and Typescript

Currently, I am utilizing the isotope-layout library within a React (Typescript) project. Although I have successfully implemented filtering on my page, I am unsure of how to retrieve the count of the filtered items. Upon loading the page, Isotope is init ...

Building TypeScript Model Classes

Greetings! As a newcomer to TypeScript with a background in both C# and JavaScript, I am on a quest to create class models resembling those found in C#. Here's my attempt so far: export class DonutChartModel { dimension: number; innerRadius: ...

Check to see if the upcoming birthday falls within the next week

I'm trying to decide whether or not to display a tag for an upcoming birthday using this boolean logic, but I'm a bit confused. const birthDayDate = new Date('1997-09-20'); const now = new Date(); const today = new Date(now.getFullYear( ...