New JavaScript developers coming from a C# or Java background often run into the problem with Cannot read property X of undefined when writing Express.js routers as ES6 classes and using dependency injection. In this post I will present two solutions to this, using a support ticketing feature as an example.

My Ticket.controller.ts file below is responsible for exposing the HTTP endpoints for my ticket API. The TicketController depends on a TicketService to be injected in the constructor. The ticket service handles all the business logic related to the feature. The business logic is separated from Express so that I can easily switch out Express if neccessary, or use the same logic for other protocols such as web sockets.

The problem

The problem with the code below is that it will result in an error like this: Cannot read property 'ticketService' of undefined This is happening because this is undefined. You can read about how this works in the MDN Web Docs. In short: the value of this depends on how the function is called, and in the example code below the function is passed to the router.get() function where the value of this refers to something else (undefined).

ticket.controller.ts

import { Router, Response, NextFunction } from 'express'
import TicketService from './TicketService';

export default class TicketController {

  public router: Router;
  private ticketService: TicketService: 
  
  constructor(service: TicketService) {
    this.router = Router();
    this.ticketService = service;
    this.routes();
  }

  private routes() {
    this.router.get('/:id', this.read);
  }

  private async read (req: Request, res: Response, next: NextFunction) {
    try {
      let ticket = await this.ticketService.get(req.params.id);
      res.send(ticket);
    } catch(err) {
      next(err)
    }
  }
}

Solution #1: binding ‘this’

The first solution is to bind this to the read method, which will make the router component instance available wherever the method is sent.

ticket.controller.ts

import { Router, Response, NextFunction } from 'express'
import TicketService from './TicketService';

export default class TicketController {

  public router: Router;
  private ticketService: TicketService: 
  
  constructor(service: TicketService) {
    this.router = Router();
    this.ticketService = service;
    this.routes();
  }

  private routes() {
    this.router.get('/:id', this.read.bind(this));
  }

  private async read (req: Request, res: Response, next: NextFunction) {
    try {
      let ticket = await this.ticketService.get(req.params.id);
      res.send(ticket);
    } catch(err) {
      next(err)
    }
  }
}

By doing so, this will no longer be undefined, and also reference the TicketComponent-object. The bind-method returns a new function with the value of this set to the provided argument.

Solution #2: arrow functions

You can also utilize arrow functions to prevent this from being undefined.

ticket.controller.ts

import { Router, Response, NextFunction } from 'express'
import TicketService from './TicketService';

export default class TicketController {

  public router: Router;
  private ticketService: TicketService: 
  
  constructor(service: TicketService) {
    this.router = Router();
    this.ticketService = service;
    this.routes();
  }

  private routes() {
    this.router.get('/:id', this.read);
  }

  private read = async (req: Request, res: Response, next: NextFunction) => {
    try {
      let ticket = await this.ticketService.get(req.params.id);
      res.send(ticket);
    } catch(err) {
      next(err)
    }
  }
}

Arrow functions keep the enclosing lexical context of this, which means that this in the function will reference whatever it referenced where it was defined.

Using arrow functions as properties comes with some caveats. Arrow functions in class properties are not in the prototype, which means other classes cannot inherit the arrow function property. More of these caveats are covered in depth in this article.

Conclusion

Based on the article in the above link, I would recommend using the .bind()-method to solve this problem.