0

I'm losing the context of the "This" object on the helper class on the following structure, and i can't figure out why.

Inside the getAll method, the this object is making reference to the object located on the servicesDict array on the main component.

I want that the this object refer the Entity1Utils class.

export class Entity1Utils {
    public getAll(context) {
        this.buildQueryParams();
        // this "this" refers to the object { id: 'entity1', method: this.entity1Utils.getAll }
        // located on servicesDict at ManagementComponent
    }

    private buildQueryParams() {
        // logical code
    }
}


@Component({
    selector: 'app-management',
    templateUrl: './management.component.html',
    styleUrls: ['./management.component.css']
})

export class ManagementComponent implements OnInit {
    private entity1Utils: Entity1Utils;
    private servicesDict: any;

    constructor() {
        this.entity1Utils = new Entity1Utils();

        this.servicesDict = [
            { id: 'entity1', method: this.entity1Utils.getAll }
        ];
    }
}
Patrick Freitas
  • 681
  • 1
  • 5
  • 18
  • I'm sorry but I don't quite understand which `this` you're talking about, where it is pointing to and where you want it to point at. – Johannes H. Aug 12 '21 at 18:57

2 Answers2

1

Generally speaking, the scope within the function (what this points to) is dictated by the object upon which the method is invoked.

foo.doSomething() // inside doSomething, 'this' is foo
const method = foo.doSomething;
method(); // 'this' is undefined
const obj = { method: foo.doSomething }
obj.method() // 'this' is obj

You're passing a reference to the method itself, detached from the instance of your class:

{ method: this.entity1Utils.getAll } // the getAll method itself

So when something downstream invokes it, it invokes it as a method on servicesDict:

const service = serviceDict[0];

// 'getAll' invoked on the serviceDict entry, so inside
// the method 'this' points to the serviceDict entry
service.method() 

You can fix this by making the method an arrow function, which binds it to the current scope:

getAll = context => { ... }

Or by creating a new anonymous inline arrow function that preserves the scope:

{ method: (...args) => this.entity1Utils.getAll(...args) }

Or by binding it explicitly:

{ method: this.entity1Utils.getAll.bind(this.entity1Utils) }
ray
  • 26,557
  • 5
  • 28
  • 27
  • Nice! I had faced this problem before, but none explanation was clean like yours. Now i got the how the context works. Thank you. – Patrick Freitas Aug 12 '21 at 19:47
  • @PatrickFreitas Which method did you use, Patrick, because the binding above would not bind to the `Entity1Utils` instance as requested, it would bind to the `ManagementComponent` instance. None of the suggested fixes here would do what you requested. – MikeM Aug 12 '21 at 20:04
  • @MikeM I actually adapted the last approach mentioned above, and just figure out now that was exactly what you suggested. – Patrick Freitas Aug 12 '21 at 20:33
  • Oops. Sorry. I overlooked the `entity1Utils` bit. Updated the answer to account for it. – ray Aug 12 '21 at 20:48
  • @PatrickFreitas I'd recommend doing the binding inside `Entity1Utils` so users of it don't have to deal with binding issues. Is there ever any reason it shouldn't be bound? – ray Aug 12 '21 at 21:13
  • Inside the getAll method I'm updating the entity list and other variables located on the ManagementComponent, besides that i also use this this as context for dependency injection. So.. on the Management: this.servicesDict[0].method(this); and on the getAll method: context.injector.get(EntityService); and context.dataList += response.results; – Patrick Freitas Aug 13 '21 at 12:23
0

Well, you could use bind, for example:

method: this.entity1Utils.getAll.bind(this.entity1Utils)
MikeM
  • 13,156
  • 2
  • 34
  • 47