Sofien
Sofien

Reputation: 478

'this' is undefined in class

Have a look at the code follows.

I am creating a class with a constructor in which I am creating some variables. Furthermore I am creating some asynchronous functions, of which 3 are very similar and the last one is able to call the others.

class Project {
    data = {};
    constructor(project, api) {
        for (const key in project) {
            this.data[key] = project[key];
        }
        this.logger = new Logger(
            document.querySelector("#output"),
            `Project= ${this.data.projectId}: `
        )
        this.api = api;
    }
    async getTasks() {
        return await this.api.getTasksAsync(this.projectId);
    }
    async getRequirements() {
        return await this.api.getRequirementsAsync(this.projectId);
    }
    async getReleases() {
        return await this.api.getReleasesAsync(this.projectId);
    }
    async getToDraw(type) {
        let func = this[`get${type}`];
        console.log(func);
        let result = [];
        for (let item of await func()){
            result.push(toTaskItem(item));
        }
        return result;
    }
}

The error I get when executing the function 'getToDraw' on an initiated object of the class 'Project' (above): Error Pic

I am calling the function as follows:

async drawDiagram(sender){
    let projectID = this.getSelectedValue("projectSelector");
    if (projectID !== "-1") {
        let project = this.projects.filter((project) => project.data.ProjectId === parseInt(projectID))[0];
        if (document.querySelector("#typeSelector").selectedOptions[0].innerText === "Full"){
            // TODO: 
        } else {
            let list = await project.getToDraw(document.querySelector("#typeSelector").selectedOptions[0].innerText);
            console.log(list);
        }
    } else {
        for (let project of this.projects) {
            // TODO:
        }
    }
}

If no one knows a solution for me, I already know how I could do it in another way, but I would like to do it like this...

Gracias, guys.

Upvotes: 2

Views: 144

Answers (2)

Thomas Arbona
Thomas Arbona

Reputation: 976

This is because when you store the getRequirements method in the func variable, you separate it from its context.

You have to reattach again the this context to func.

First way: attach context only once

You just have to call the func function like this:

func.call(this); // call func with this as context
func(); // call func with undefined context
func.call({}); // call func with empty object as context

This way, you force calling func with this as context.

Second way: attach context for all future calls

You have to bind a new context before calling the function:

func(); // call func with undefined context
func = func.bind(this);
func(); // call func with this as context
func(); // recall func with this as context

This way, you link this as new context for func.

Upvotes: 4

p.s.w.g
p.s.w.g

Reputation: 149040

Look at this line

let func = this[`get${type}`];

It unbinds (or 'disconnects') the function from the this reference to the class so that when you call func(), this becomes undefined.

There are a couple ways to get around this. You could re-bind this using bind, like this:

let func = this[`get${type}`].bind(this);

Or you could explicitly rebind the methods within the constructor:

class Project {
  constructor(project, api) {
    ...
    this.getTasks = this.getTasks.bind(this);
    this.getRequirements = this.getRequirements.bind(this);
    this.getReleases = this.getReleases.bind(this);
  }
}

Or you could define your methods as lambda expression properties, like this:

class Project {
  data = {};
  getTasks = () => { ... };
  getRequirements = () => { ... };
  getReleases = () => { ... };
}

These are not the only solutions, of course, and they may have some different side-effects. For example, the lambda expression properties are enumerable, so they will appear in Object.keys(new Project()).

Upvotes: 4

Related Questions