Of all of TypeScript’s (TS) many notable features, the fat arrow is arguably one of the most powerful, yet it is also the most confusing to newbies to the TS language. For those reasons, it would be a valuable exercise to explore the fat arrow’s role and usage in TS.
Fat Arrow Basics
The fat arrow (=>) was named as such to differentiate it from the thin arrow (->). It was introduced as part of the ECMAScript 6 (ES6) specification, which TS extends, as a shorter way to define anonymous functions. Not surprisingly, functions that use the fat arrow are referred to as fat arrow functions. The impetus to the advent of the fat arrow function was a simple one: in JavaScript you tend to type the word “function” quite a lot. Anonymous functions save you from having to assign a name to the function, but the “function” part is still required. The fat arrow allows you to create a function sans the “function” keyword.
Here’s a simple example:
It’s standard practice in JavaScript to pass an anonymous function to the setTimeout() function:
setTimeout(function() { console.log("setTimeout called!"); }, 1000);
In TS, we can re-write our function call as:
setTimeout(() => { console.log("setTimeout called!") }, 1000);
As you can see, we omitted the “function” keyword and inserted the fat arrow between the argument parentheses and opening curly brace.
It gets even better; if the function only contains a one-line expression, we can drop the curly braces as well to shorten the code even further to:
setTimeout(() => console.log("setTimeout called!"), 1000);
Arguments & Return Values
Although the fat arrow syntax dispenses with the “function” keyword, it keeps the parentheses in place for arguments. Consider this simple concat() function, which accepts two strings to join together:
//JS var concat = function(str1,str2) { return str1 + str2; }; //TS using the fat arrow: let concat = (str1,str2) => str1 + str2;
In the spirit of making the code as concise as possible, if the function body is a one liner, the statement gets returned automatically without the need to use the return keyword!
The this Context
In JavaScript, the value of this is largely determined by how a function is called. There are also differences between strict mode and non-strict mode.
In strict mode, this will be undefined, because strict mode does not allow default binding:
"use strict"; function myFunction() { return this; } alert(myFunction()); //undefined!
In non-strict mode, this will be set to the global window object by default.
function myFunction() { return this; } alert(myFunction()); //[object Window]
If you want to change the this context, you have to pass it to the function using the call() and apply() methods or capture it using a closure or some such technique.
The following method call doesn’t do what we expect it to because the this context reverts back to the global window object:
function Person(age) { this.age = age; this.turnOneYearOlder = function() { alert( this.age++ ); // NaN! } } let person = new Person(1); setTimeout(person.turnOneYearOlder, 1000);
By capturing the this context, we can refer back to it later when we need it:
function Person(age) { this.age = age; var person = this; // capture this this.turnOneYearOlder = function() { alert( person.age++ ); // 1 } } let person = new Person(1); setTimeout(person.turnOneYearOlder,1000);
TypeScript sidesteps this entire issue by having the fat arrow function maintain the this context:
class Person { constructor(public age:number) {} turnOneYearOlder = () => { this.age++; } } let person = new Person(1); setTimeout(person.turnOneYearOlder, 1000); //1
Whether you call the function yourself or pass it to another function to call, this is going to be the correct calling context:
let turnOneYearOlder = person.turnOneYearOlder; // Then later someone else calls it: let ageMe = (fn) => fn(); setTimeout(() => ageMe(turnOneYearOlder), 1000);
Using Arrow functions with JS Libraries
Many libraries, such as jQuery, set the this context within their iteration methods so that you can reference the current object being iterated over. JQuery.each() is one such method. Therefore, should you wish to reference the enclosing object’s context you have to store it in a local variable just as you would when not using an arrow function.
Here’s some code to illustrate:
class Person { constructor(public age:number, public emails:string[]) {} turnOneYearOlder = () => { this.age++; } listEmails = () => { console.log(this); //Person $.each( this.emails, ( index, email ) => { //these 2 lines won't work! this points to email this.turnOneYearOlder(); console.log(this.age); }); } } let person = new Person(1, ['elmer@acme.com', 'ejf@warner.com', 'efudd@aol.com']);
The fix is to store the Person context before invoking $.each():
listEmails = () => { let person = this; $.each( this.emails, ( index, email ) => { //this works person.turnOneYearOlder(); console.log(person.age); }); }
The code presented in this article is also available on Codepen, where you can see it in action.
Conclusion
As we saw here today, the TypeScript fat arrow provides several notable benefits to us developers. Master its syntax and writing code will be several orders of magnitude easier than ever before!