0

I want to pass a function that has an http request from a parent component to a child component, and from the child component execute the request.

My code as the following:

// myService
constructor(private http: HttpClient)
getIds(id) {
    // return of(['a', 'b', 'c', id]); with this works
    return this.http.get(apiUrl)  // with this doesnt work
 }

// parentComponent.ts
callbackFunction = this.myService.getIds;
constructor(private myService: MyService) { }

// parentComponent.html
<child-component [callbackRequest]="callbackFunction"></child-component>

// childComponent.ts
@Input() callbackRequest;
ngOnInit() {
   this.callbackRequest('d');
}

What confuses me the most is that if from the service I return an observable built from of, if it works. When debugging I see that if the call to the service arrives. The error I get is the following: ERROR TypeError: Cannot read property 'get' of undefined

juanjinario
  • 596
  • 1
  • 9
  • 24

3 Answers3

1

You can do it:

// myService
constructor(private http: HttpClient)
getIds(id) {
    // return of(['a', 'b', 'c', id]); with this works
    return this.http.get(apiUrl)  // with this doesnt work
 }

// parentComponent.ts
callbackFunction = this.myService.getIds.bind(this.myService);
constructor(private myService: MyService) { }

// parentComponent.html
<child-component [callbackRequest]="callbackFunction"></child-component>

// childComponent.ts
@Input() callbackRequest;
ngOnInit() {
   this.callbackRequest('d');
}

.bind(this) is the solution, because the function will be able to use the parent instance (you can see that everything turn undefined when you don't bind it)

Gabriel Sereno
  • 845
  • 1
  • 5
  • 6
  • It has not worked with .bind(this), it worked with this.myService.getIds.bind(this.myService); I don't understand it, I don't understand why it works with of [...] – juanjinario May 04 '21 at 13:23
  • It will work because you are appointing your function to use your service's instance. If you don't do that, you're talking to Angular that the function is a local function that won't read global variables and functions. When you pass something with bind, you're wanting that Angular to use this instance to provide what you want (in this case was the HttpClient) – Gabriel Sereno May 04 '21 at 13:29
  • You can see more explanations here: https://stackoverflow.com/questions/26477245/when-to-use-bind-in-js – Gabriel Sereno May 04 '21 at 13:29
1

The meaning of this keyword inside the getIds function is lost when it's reference is passed around using the @Input binding in Angular. It works when you return of([...]) because there is no usage of this. See here for a canonical post on the meaning of this keyword in a callback

There are two solutions

  1. Using bind - see the post from @GabrielSereno
  2. Use arrow functions

Service

public getIds = (id) => {
  return this.http.get(apiUrl);
}

Component

callbackFunction: any;;
constructor(private myService: MyService) { }

ngOnInit() {
  this.callbackFunction = this.myService.getIds;
}

Template

<ng-container *ngIf="callbackFunction">
  <child-component [callbackRequest]="callbackFunction"></child-component>
</ng-container>
ruth
  • 29,535
  • 4
  • 30
  • 57
0

Is there a specific reason to work with a callback function?

Normaly i would try to make one component "smart" and one "dumb".
In your case the parent is the one which decides WHAT to do (which HTTP request) and the child decides WHEN to do it. And perhaps the child also should show the result.
Therefore i decided in a similar case, that my child will inform the parent when its time to run the request, then the parent runs the request and provides the result to the child.

Yes, that way i need not just one INPUT, but one INPUT and an OUTPUT for my child. On the other side, my child has a cleanly defined INPUT interface. If i just provide a callback method, the call back may return anything.

Also my child has nearly no "logic" in it. All the magic is in the parent. That makes it quite easy to write UnitTests for the child component. And also for the parent its quite easy with just a mocked child.

Yes, JavaScript (and therefore also TypeScript) allow things like .bind(). But in my experience it seems to solve a problem quite fast, but the real effort comes later. Its harder to understand and its harder to debug. Therefore i normaly avoid it.

Just another way to solve a problem. Pick the parts you like and igore the rest :- )

JanRecker
  • 1,787
  • 13
  • 17