Angular — Why you should (and how to) avoid Function.prototype.bind in template

Function.prototype.bind is a built-in function, which creates a new function with the context bound to the context given from the parameter. The major use case for this function in Angular template is the property binding (@Input) of function.

<app-test-component 
[testFunction]="printValue.bind(this)"></app-test-component>

This helps to maintain the context scope when the function is executed inside the component. It is a relatively uncommon function nowadays that some Angular developers may not have even seen it before. Yet, if you have been using it in your templates, I highly recommend you to stop doing that as it leads to performance issue.

The Issue

In short, the root problem is that function calls in template are not friendly to the Angular change detection mechanism. Each time change detection is triggered, all the function calls in template will always be re-executed, which includes Function.prototype.bind. For a more detailed explanation, please check out another article I have written previously:

As Function.prototype.bind is a relatively slow function, it can become a performance killer very easily. Here is a simple example to demonstrate the issue. To begin with, we modify the built-in function to add a log, such that we know when it is called:

let calledTimes = 0;
const originalBind = Function.prototype.bind;
Object.defineProperty(
Function.prototype,
'bind',
{
value: function bind(context) {
console.log(`bind called ${++calledTimes} times`);
return originalBind.apply(this, arguments);
}
}
);

Next, we create a simple component which is capable of accepting a Function property:

@Component({
selector: 'app-test-component',
template: `
<button (click)="testFunction()">Call function</button>
`
})
export class TestComponent {
@Input()
testFunction = () => {};
}

Finally, we add this component to our root:

@Component({
selector: 'app-root',
template: `
<button (click)="triggerChangeDetection()">
Trigger change detection
</button>
<app-test-component
[testFunction]="printValue.bind(this)"></app-test-component>
`
})
export class AppComponent {
value = 1;

triggerChangeDetection() {
console.log("Change detection triggered");
}

printValue() {
console.log(this.value);
}
}

Now, we can try to trigger the change detection by clicking the button.

Every time when the button is clicked (which triggers a change detection), the Function.prototype.bind function will always be re-executed.

(It is expected to have the function called twice per detection cycle in development mode. It is an Angular debugging feature. Here is a more detailed explanation.)

It becomes a disaster when there is feature that triggers change detection frequently, such as listening to mousemove events.

@Component({
template: `
<button (mousemove)="triggerChangeDetection()">
Trigger change detection
</button>
...
`,
...
})
export class AppComponent {...}

The Solution

The way to solve this issue is actually simpler than you can imagine, which is simply don’t use Function.prototype.bind. The major reason we use bind here is to maintain the this context scope, but there is a much cleaner alternative. What we need to do is to turn the ordinary function:

printValue() {
console.log(this.value);
}

into an arrow function that doesn’t create an extra context scope:

printValue = () => {
console.log(this.value);
}

Now, we can just apply property binding on the arrow function without the need to worry about the context:

@Component({
template: `
...
<app-test-component
[testFunction]="printValue"></app-test-component>
`
})
export class AppComponent {...}

Thanks for reading! Hope you find this article helpful. Any comments would be highly appreciated. :D

Web developer from Hong Kong. Most interested in Angular and Vue. Currently working on a Nuxt.js + NestJS project.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store