15

I've a component that takes function as input. I've passed this function from parent.

Though the function is called, the function is not able to access the dependencies of the instance on which this function is declared.

Here is the component

@Component({
  selector: 'custom-element',
  template: `
    {{val}}
  `
})
export class CustomElement {
  @Input() valFn: () => string;

  get val(): string {
    return this.valFn();
  }
}

Here is how the component is used

@Injectable()
export class CustomService {
  getVal(): string {
    return 'Hello world';
  }
}

@Component({
  selector: 'my-app',
  template: `
   <custom-element [valFn]="customVal"></custom-element>
  `,
})
export class App {
  constructor(private service: CustomService) {
  }
  customVal(): string {
    return this.service.getVal();
  }
}

When I run this app, I get an error in the console saying Cannot read property 'getVal' of undefined

Here is a plunker for the issue.

https://plnkr.co/edit/oQ229rXqOU9Zu1wQx18b?p=preview

Ashok Koyi
  • 5,327
  • 8
  • 41
  • 50

2 Answers2

42

You need to .bind(this) if you pass methods around:

<custom-element [valFn]="customVal.bind(this)"></custom-element>

or

export class App {
  constructor(private service: CustomService) {
  }
  customVal(): string {
    return this.service.getVal();
  }
  customValFn = this.customVal.bind(this);
}

with

<custom-element [valFn]="customValFn"></custom-element>
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks :). That works. I thought the context on which this function is run is already set as it is referred on the `App` instance – Ashok Koyi Mar 03 '17 at 09:16
  • Any idea on why `this` context is lost in case of function, whereas it can refer to correct `this` incase of properties? – Ashok Koyi Mar 03 '17 at 09:17
  • 2
    This is "normal" ;-) or "default" JS behavior. If you pass methods or functions around that refer to `this`, you have to either use `.bind(this)` or arrow functions (like `[valFn]="() => customVal()"` but that's not supported in template bindings AFAIR. – Günter Zöchbauer Mar 03 '17 at 09:19
  • 4
    The "default" behavior is that `this` points to the caller of the function, not the declarer. – Günter Zöchbauer Mar 03 '17 at 09:21
  • I want to add one more thing, if you inject CustomerService into your custom-element component then it will work fine, in that case you need not to bind this explicitly. @gunter is right BTW, **this** holds the context of the current class not the class where method is declared. – Ankit Oct 12 '17 at 20:41
  • Is this preferred, ie. why not setup an emitter ? – Steve Apr 30 '18 at 16:05
  • @Steve event binding is definitly preferred if possible, but if someone asks for passing methods I try to answer that. – Günter Zöchbauer Apr 30 '18 at 18:53
  • Hi @GünterZöchbauer, if an emitter is the preferred solution, can you please provide an example? or any reference? thank you – Yukun Aug 23 '18 at 06:10
  • @Yukun is there an Angular introductorial that doesn't explain `@Output()` with `EventEmitter`? – Günter Zöchbauer Aug 23 '18 at 06:12
1

You can pass a get/set property instead of a function in a similar way like that:

Somewhere in your view:

<input type="text" [(ngModel)]="yourprop">

In your component file:

@Component({
  selector: 'myapp',
  templateUrl: './myapp.component.html',
  styleUrls: ['./myapp.component.scss']
})
export class App {
  constructor() { }

  yourprop: any;

  get yourprop(): any {
    return this.scheduleEndDate;
  };

  //set accessor including call the onchange callback
  set yourprop(v: any) {
    // TODO do something else
    // You can do whatever you want just like you have passed a function

    if (v !== this.scheduleEndDate) {
      this.scheduleEndDate = v;
    }
  }

}

more info @ https://almerosteyn.com/2016/04/linkup-custom-control-to-ngcontrol-ngmodel

Combine
  • 3,894
  • 2
  • 27
  • 30