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

2 Answers 2

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>
Sign up to request clarification or add additional context in comments.

9 Comments

Thanks :). That works. I thought the context on which this function is run is already set as it is referred on the App instance
Any idea on why this context is lost in case of function, whereas it can refer to correct this incase of properties?
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.
The "default" behavior is that this points to the caller of the function, not the declarer.
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.
|
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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.