I was learning some Angular.js for Reactify migration and noticed that when I define a component with template and controller like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
phonecatApp.component('phoneList', {
templateUrl: `<ul>
<li ng-repeat="phone in $ctrl.phones">
<span> {{phone.name}} </span>
<span>{{phone.snippet}} </span>
</li>
</ul>
`,
controller: () => {
this.phones = [
{
snippet: 'Fast just got faster with Nexus S.',
},
{
name: 'Motorola XOOM™ with Wi-Fi',
snippet: 'The Next, Next Generation tablet.',
},
{
name: 'MOTOROLA XOOM™',
snippet: 'The Next, Next Generation tablet.',
},
]
},
})

The phone list cannot be rendered. But this works:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
phonecatApp.component('phoneList', {
templateUrl: `<ul>
<li ng-repeat="phone in $ctrl.phones">
<span> {{phone.name}} </span>
<span>{{phone.snippet}} </span>
</li>
</ul>
`,
controller: function () {
this.phones = [
{
snippet: 'Fast just got faster with Nexus S.',
},
{
name: 'Motorola XOOM™ with Wi-Fi',
snippet: 'The Next, Next Generation tablet.',
},
{
name: 'MOTOROLA XOOM™',
snippet: 'The Next, Next Generation tablet.',
},
]
},
})

Here’s why

There’s no this binding with fat arrow function

https://www.w3schools.com/js/js_arrow_function.asp#:~:text=In%20short%2C%20with%20arrow%20functions,that%20defined%20the%20arrow%20function.

In regular functions the this keyword represented the object that called the function, which could be the window, the document, a button or whatever.

With arrow functions the this keyword always represents the object that defined the arrow function.

If arrow function is called with fn.apply(self, args) the self arg will be ignored

If I define these two functions:

1
2
> arrowFn = () => console.log(this)
> fn = function(){console.log(this)}

If I call them directly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
> fn()
<ref *1> Object [global] {
global: [Circular *1],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
queueMicrotask: [Function: queueMicrotask],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
arrowFn: [Function: arrowFn],
fn: [Function: fn]
}
> arrowFn()
<ref *1> Object [global] {
global: [Circular *1],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
queueMicrotask: [Function: queueMicrotask],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
arrowFn: [Function: arrowFn],
fn: [Function: fn]
}

I get the global scope for this.

If I call fn with apply, I can change this:

1
2
> fn.apply("hello", [])
[String: 'hello']

If I call arrowFn with apply, this is still global:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> arrowFn.apply("hello", [])
<ref *1> Object [global] {
global: [Circular *1],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
queueMicrotask: [Function: queueMicrotask],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
arrowFn: [Function: arrowFn],
fn: [Function: fn]
}

Angular uses apply to define the scope for controller

Angular calls the controller function like this: fn.apply(self, args); where self (which becomes this in the invoked function) is an object that has the required fields

https://stackoverflow.com/questions/35176469/passing-scope-variables-to-an-angularjs-controller-using-fat-arrows

Can I use arrow function with . definition

Short answer: no.

function definition works as expected:

1
2
3
4
5
> const obj = {}
> obj.fn = function(){console.log(this)}

> obj.fn()
{ fn: [Function (anonymous)] }

arrow function still only remembers global for this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> obj.arrowFn = () => console.log(this)

> obj.arrowFn()
<ref *1> Object [global] {
global: [Circular *1],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
queueMicrotask: [Function: queueMicrotask],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
}
}

Key takeaways

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#Invoked_through_call_or_apply

Arrow functions don’t have their own bindings to this, arguments or super, and should not be used as methods.

Arrow functions aren’t suitable for call, apply and bind methods, which generally rely on establishing a scope.

Good user cases of arrow function

  • More consise promise chains
  • Easy array filter, mapping, reducing
  • Immediately invoked function expression
  • Parameterless functions as call back arguments that are easier for eyes