1

In the code below I get 'undefined' error for 'loglogMePleasePlease' function.

Could someone please help me out with this.

TypeError: Cannot read property 'logMePleasePlease' of undefined

I am at flabbergasted, this kinda error makes me question everything I have coded so far.

import {Router, Request, Response, NextFunction} from 'express';
import * as fs from 'fs';
import { db } from '../db/lowDb'

export class employeeRoute {
    router: Router
    constructor() {
        this.router = Router();
        this.init();
    }
    init() {
        this.router.get('/', this.default);
        this.logMePleasePlease('SFDSFSDF');/*This call works fine!*/
    }
    public logMePleasePlease(err){
        console.log(err);
    }
    public default(req: Request, res: Response, next: NextFunction) {
        /*****Error when execution gets to follwing line:
        TypeError: Cannot read property 'logMePleasePlease' of undefined
        ******/
        this.logMePleasePlease('asdad');
        res.send({
            status:'ok',
            message:'employee api home'
        });
    }
}
const employee = new employeeRoute();
export default employee.router;
Murat Karagöz
  • 35,401
  • 16
  • 78
  • 107
zion inc
  • 145
  • 1
  • 1
  • 9

3 Answers3

0

That's probably because your router messes up the context of the function 'default', that's why this equals undefined.

You can try binding the func to the right context in the constructor:

this.default.bind(this)

But this is messy. Are you sure it's a good idea to have a separate router for every route? I would create the one and only router and provide it to every route class in constructor..

I'm using to following pattern in creating routes taken from one of the popular tutorials, which is a bit arguable because of extensive use of static methods, but works nicely for me:

import { NextFunction, Request, Response, Router } from 'express'


export class IndexRoute {


    static CREATE(router: Router) {
        console.log('[IndexRoute::create] Creating route /');

        router.get('/', (req, res, next) => {
            new IndexRoute().index(req, res, next)
        })
    }


    index(req: Request, res: Response, next: NextFunction) {
        console.log('[IndexRoute::index]');
        const data = { status: 'ok' };
        res.status(200).json(data);
    }

}
Daniel Khoroshko
  • 2,623
  • 15
  • 26
  • Here is a similar question https://stackoverflow.com/questions/15604848/express-js-this-undefined-after-routing-with-app-get – Daniel Khoroshko Aug 19 '17 at 09:04
  • Good suggestion Daniel, I will update constructor to take in router. (And thanks for quick answer, I was about to grab jumbo pack of 'Lays chips' to binge on) – zion inc Aug 19 '17 at 09:22
0

This is caused by the way JavaScript binds this when calling a function.

Let's take a couple of examples and show what I mean as well as how to go about fixing it.

class BindingTest {
  func() {
    console.log(this)
  }
}

const test = BindingTest()
const testFunc = test.func

Now, as you can see in this example, we have the test object which presents the func method, as well as the testFunc variable which holds a reference to that method.

It is important to keep in mind the way that JavaScript moves values around (and adds functions to classes), specifically that everything is a value. In this case, when we assign const testFunc = test.func, what we're doing is taking the BindingTest.prototype.func value (the function we defined on the class) and referencing it directly.

As a consequence, when we run the following:

testFunc()

We will see that it prints out undefined rather than the BindingTest context...

That's rather odd, because when we run the following:

test.func()

We get the context printed!

So as I alluded to, this is the result of calling the function via the object's prototype invocation strategy (in which case this is bound to test) and calling the function as an isolated value (in which case this is undefined).

The fix is actually very simple, JavaScript provides the .bind() method which allows you to associate a this context with a function value.

const boundTestFunc = test.func.bind(test)

Now, if we go and call boundTestFunc, we'll get our BindingTest context printed out as we would expect.

I hope this clarifies the "why" behind what you're seeing. In your case: the fix will be to simply use the following code in your constructor.

this.router.get('/', this.default.bind(this));
Benjamin Pannell
  • 3,645
  • 2
  • 19
  • 22
0

In case this helps anyone, here is sample code using classes and the use method on a Router (see example at bottom of documentation here).

server.ts

import { Test } from './test';
import express from "express";
import compression from "compression";

export class Server {
    private app: express.Application;
    private port: number = 3000;
    private test: Test;

    constructor() {
        // setup server
        this.app = express();
        this.app.use(express.urlencoded({ extended: true }));   // needed for POST requests
        this.app.use(express.json());   // needed for POST requests
        this.app.use(compression());

        // create classes
        this.test = new Test();

        // tell app to use routes
        this.app.use("/test", this.test.router);

        // start listening
        this.app.listen(this.port, () => {
            console.log("Node Express server listening on port " + this.port);
        });
    }

}

test.ts

import express, { Router, Request, Response } from "express";

export class Test {
    public router: Router = express.Router();
    private testVar: string = "Hello World";

    constructor() {
        this.router.get('/', [this.get.bind(this)]);
    }

    private async get(req: Request, res: Response) {
        try {
            // return
            res.status(200).send(this.testVar);
        } catch (error) {
            res.status(500).send(error);
        }
    }

}
xinthose
  • 3,213
  • 3
  • 40
  • 59