dzenesiz
dzenesiz

Reputation: 1552

Angular – loosing reference to `this` when passing function as parameter

I am building an Angular application, and I have two services, call them ServiceA and ServiceB. ServiceB has two methods that ServiceA uses in the same way, something like this:

@Injectable({
    providedIn: 'root'
})
export class ServiceA {
    constructor(private _b: ServiceB) {}

    doFoo(input: Whatever): void {
        this._b.foo(input)
            .pipe(...);
            .subscribe(...);
    }

    doBar(input: Whatever): void {
        this._b.bar(input)
            .pipe(...); // exact same pipe as above
            .subscribe(...); // exact same subscribe as above
    }
}

Since I have duplicate logic, I tried to do this in ServiceA:

performFooBarAction(input: Whatever, fooBarFn: (input: Whatever) => Observable<void>): void {
    fooBarFn(input).pipe(...).subscribe(...);
}

And call it from both methods like this:

doFoo(input: Whatever): void {
    this.performFooBarAction(input. this._b.foo);
}

But, if I do it like that I get an error:

Cannot read properties of undefined

So, I tried to add console.log(this) to serviceB.foo(), and – lo and behold – this is undefined when I pass the function as an argument. It worked before my little refactor.

Any idea if this is unavoidable, or is it me who's doing it wrong?

Upvotes: 2

Views: 1547

Answers (3)

icekson
icekson

Reputation: 371

bind context when passing function as an argument:

 doFoo(input: Whatever): void {
        this.performFooBarAction(input. this._b.foo.bind(this._b));
    }

Upvotes: 1

dzenesiz
dzenesiz

Reputation: 1552

I managed to solve this by invoking the call method and passing it the reference to ServiceB as context, like so:

performFooBarAction(input: Whatever, fooBarFn: (input: Whatever) => Observable<void>): void {
    fooBarFn.call(this._b, input).pipe(...).subscribe(...);
}

But if someone can tell me if there is a better way, I'd be thankful.


[EDIT] - Another solution

Since I discovered I actually won't have the same parameter list for foo() and bar(), I came up with another way, which doesn't require context passing, and works in both cases - I simply passed the Observables that come from _b.foo() and _b.bar() like this:

handleFooBarAction(input$: Observable<void>): void {
  input$.pipe(...).subscribe(...);
}

Which I can now call like this:

doFoo(input: Whatever): void {
    this.handleFooBar(this._b.foo(input));
}

doBar(): void { // no params
    this.handleFooBar(this._b.bar()); // no params
}

Upvotes: 0

Apoorva Chikara
Apoorva Chikara

Reputation: 8773

You can also try this:

@Injectable({
    providedIn: 'root'
})
export class ServiceA {
    constructor(private _b: ServiceB) {}

    doFoo(input: Whatever): void {
        this._b.foo(input)
            .pipe(...);
            .subscribe(...);
    }

    doBar(input: Whatever): void {
        this._b.bar(input)
            .pipe(...); // exact same pipe as above
            .subscribe(...); // exact same subscribe as above
    }

    callMethodByName(input: Whatever, funcName: string) {
        this._b[funcName](input).pipe().subscribe();
    }

}

A working sample is attachted below:

class Testing {

logName(name) {
    console.log(`Name is ${name}`);
}

logAge(age) {
    console.log(`Age is ${age}`);
}

print(funcName, data) {
   this[funcName](data);
}
}

const tes = new Testing();
tes.print('logName', 'Apoorva Chikara');
tes.print('logAge', 29)

Upvotes: 1

Related Questions