The this mystery - why fat arrow does not work for Angularjs controller
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