Angular2 passing function as component input is not working
Asked Answered
P

2

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

Powerboat answered 3/3, 2017 at 9:10 Comment(0)
N
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>
Nominate answered 3/3, 2017 at 9:14 Comment(9)
Thanks :). That works. I thought the context on which this function is run is already set as it is referred on the App instancePowerboat
Any idea on why this context is lost in case of function, whereas it can refer to correct this incase of properties?Powerboat
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.Kern
The "default" behavior is that this points to the caller of the function, not the declarer.Kern
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.Rhizo
Is this preferred, ie. why not setup an emitter ?Meperidine
@Meperidine event binding is definitly preferred if possible, but if someone asks for passing methods I try to answer that.Kern
Hi @GünterZöchbauer, if an emitter is the preferred solution, can you please provide an example? or any reference? thank youLento
@Lento is there an Angular introductorial that doesn't explain @Output() with EventEmitter?Kern
G
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

Germayne answered 23/10, 2018 at 13:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.