Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Arrow Functions

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

You will see the first big feature or ES2015 used everywhere... and at first, it looks weird. Very simply, there is a new, shorter syntax for creating anonymous functions.

For example, in our .then(), we have an anonymous function:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 31
loadRepLogs: function() {
var self = this;
$.ajax({
url: Routing.generate('rep_log_list'),
}).then(function(data) {
$.each(data.items, function(key, repLog) {
self._addRow(repLog);
});
})
},
... lines 42 - 173
});
... lines 175 - 191
})(window, jQuery, Routing, swal);

In ES2015, we can remove the word function, and add an "equal arrow" (=>) after the arguments:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 31
loadRepLogs: function() {
var self = this;
$.ajax({
url: Routing.generate('rep_log_list'),
}).then((data) => {
$.each(data.items, function(key, repLog) {
self._addRow(repLog);
});
})
},
... lines 42 - 173
});
... lines 175 - 191
})(window, jQuery, Routing, swal);

That's it! That will do the exact same thing as before. Well, PhpStorm is really angry about this, but ignore it for a second. Let's try it! This loadRepLogs() function is called on page-load to populate the table. Refresh!

It works: no errors.

Make PhpStorm Less Angry

But, apparently PhpStorm hates the arrow function! That's because it's setup to only recognize old, ES5 JavaScript.

Go into your settings and search for ES6. Under "Languages & Frameworks", "JavaScript", you can choose what version it should use. Let's go with "ECMAScript 6". Hit ok... and once it's done indexing... ding! It's happy! And I'm happy too!

If you see a bubble about a "File Watcher to transpile using Babel", ignore that! But, we will talk about that "Babel" thing later, it's more than just a cool-sounding word. Babel.

Different Arrow Syntaxes

So the arrow syntax is nothing Earth-shattering. But it's used a lot, so you need to train your eyes to recognize that it's just an anonymous function.

And sometimes, it can look a bit different. For example, the parentheses around the arguments? Totally optional! Without them, everything still works:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 31
loadRepLogs: function() {
var self = this;
$.ajax({
url: Routing.generate('rep_log_list'),
}).then(data => {
$.each(data.items, function(key, repLog) {
self._addRow(repLog);
});
})
},
... lines 42 - 173
});
... lines 175 - 191
})(window, jQuery, Routing, swal);

I like the parentheses: I feel like it gives my arrow functions a bit more structure. But other code might not have them.

The Arrow Function's (Secret) Superpower (this)

Now if this were all the arrow function did, I would be pretty disappointed. After all, did we really need a new syntax, just to save us from typing the word function? Well don't worry, because the arrow function has one, very amazing super power.

To show it off, inside of the anonymous function, console.log(this, self):

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 31
loadRepLogs: function() {
var self = this;
$.ajax({
url: Routing.generate('rep_log_list'),
}).then(data => {
console.log(this, self);
... lines 38 - 40
})
},
... lines 43 - 174
});
... lines 176 - 192
})(window, jQuery, Routing, swal);

We know that inside of an anonymous function, this always changes to be something different. And that's why we added the self variable: it allows us to refer to our RepLogApp object from inside the callback.

Ok, find your browser and refresh! Woh, check this out: this appears to be our RepLogApp object! Yea, this and self are the same thing! What!?

It turns out, a classic anonymous function and the new arrow function do have one difference: when you use an arrow function, the this variable is preserved. That's awesome news, and it's why I now use the arrow function everywhere in my code.

We can finally remove this silly var = self thing. And instead, below, use this. But because we're inside of another anonymous function, replace it with the new arrow syntax to get things work:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 31
loadRepLogs: function() {
$.ajax({
url: Routing.generate('rep_log_list'),
}).then(data => {
$.each(data.items, (key, repLog) => {
this._addRow(repLog);
});
})
},
... lines 41 - 172
});
... lines 174 - 190
})(window, jQuery, Routing, swal);

Try that out! It still works!

Arrow Functions Everywhere!

Let's use the arrow syntax everywhere! Below, when we use SweetAlert, remove the self variable and - in preConfirm - use () for empty arguments, then add the arrow. Inside, we can use this! Use the arrow function again below:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 47
handleRepLogDelete: function (e) {
... lines 49 - 52
swal({
... lines 54 - 57
preConfirm: () => {
return this._deleteRepLog($link);
}
}).catch((arg) => {
// canceling is cool!
});
},
... lines 65 - 170
});
... lines 172 - 188
})(window, jQuery, Routing, swal);

Here, we're not using this, but I like to stay consistent and use the arrow function everywhere.

Keep going! Inside the next method, remove self, and add our arrow function:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 65
_deleteRepLog: function($link) {
... lines 67 - 78
}).then(() => {
... lines 80 - 83
})
},
... lines 86 - 170
});
... lines 172 - 188
})(window, jQuery, Routing, swal);

Do the same for fadeOut(). But here, we were using this, which previously pointed to the DOM Element object that was fading out. We can't use this anymore, but that's fine! Replace it with $row.remove() and then this.updateTotalWeight():

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 65
_deleteRepLog: function($link) {
... lines 67 - 78
}).then(() => {
$row.fadeOut('normal', () => {
$row.remove();
this.updateTotalWeightLifted();
});
})
},
... lines 86 - 170
});
... lines 172 - 188
})(window, jQuery, Routing, swal);

Double-check that things work. Refresh! Delete one of the items and... perfect!

Since we're going to use arrow functions for all anonymous functions, search for function(). Yep, we're going to replace everything, except for the methods in our objects. Remove function(), and add the arrow:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 90
handleNewFormSubmit: function(e) {
... lines 92 - 95
$.each($form.serializeArray(), (key, fieldData) => {
... line 97
});
... lines 99 - 106
},
... lines 108 - 170
});
... lines 172 - 188
})(window, jQuery, Routing, swal);

Repeat it again, and remove another self variable: just use this:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 90
handleNewFormSubmit: function(e) {
... lines 92 - 100
.then((data) => {
this._clearForm();
this._addRow(data);
}).catch((errorData) => {
this._mapErrorsToForm(errorData.errors);
});
},
... lines 108 - 170
});
... lines 172 - 188
})(window, jQuery, Routing, swal);

I'll fast-forward through the rest of the changes.

Looping without Using this

If you were watching really closely, you may have noticed a problem. Before, inside the $.each() callback, this was the element that we were iterating over at that exact time:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 178
$.extend(Helper.prototype, {
calculateTotalWeight: function() {
... line 181
this.$wrapper.find('tbody tr').each(function () {
totalWeight += $(this).data('weight');
});
... lines 185 - 186
}
});
})(window, jQuery, Routing, swal);

But now that we're using the arrow function, obviously, that won't work. No worries! Just give your arrow function two arguments: index and element. Use element instead of this:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 178
$.extend(Helper.prototype, {
calculateTotalWeight: function() {
... line 181
this.$wrapper.find('tbody tr').each((index, element) => {
totalWeight += $(element).data('weight');
});
... lines 185 - 186
}
});
})(window, jQuery, Routing, swal);

If we search for .each(), there is one other spot with the same problem. Same solution: add index, element and use element inside:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 129
_mapErrorsToForm: function(errorData) {
... lines 131 - 133
$form.find(':input').each((index, element) => {
var fieldName = $(element).attr('name');
var $wrapper = $(element).closest('.form-group');
... lines 137 - 145
});
},
... lines 148 - 170
});
... lines 172 - 188
})(window, jQuery, Routing, swal);

Arrow Function without a Body

Now that we're using the arrow function everywhere, there's one more variation that you'll see. Scroll back up and find the preConfirm option on SweetAlert:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 47
handleRepLogDelete: function (e) {
... lines 49 - 52
swal({
... lines 54 - 57
preConfirm: () => {
return this._deleteRepLog($link);
}
... lines 61 - 62
});
},
... lines 65 - 170
});
... lines 172 - 188
})(window, jQuery, Routing, swal);

In this case, the arrow function is nothing more than a single return statement. In this situation, to be extra fancy, you can remove the function body and return statement entirely:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 47
handleRepLogDelete: function (e) {
... lines 49 - 52
swal({
... lines 54 - 57
preConfirm: () => this._deleteRepLog($link)
... lines 59 - 60
});
},
... lines 63 - 168
});
... lines 170 - 186
})(window, jQuery, Routing, swal);

When you don't have the curly braces, it means that this value will be returned. It looks weird at first, but it means the same thing that we had before. You will see this kind of stuff in code examples.

Phew! After making all these changes, let's refresh and try them. The list loads, we can delete, and the form still validates. Bananas!

Next, I think we should write some Node.js!

Leave a comment!

8
Login or Register to join the conversation
Lino M. Avatar
Lino M. Avatar Lino M. | posted 1 year ago

Hello, I have : RepLogApp.js:166 Uncaught ReferenceError: Routing is not defined at RepLogApp.js:166
I Don't know what to do.

Reply

Hey Developper,

Did you download the course code and started from start/ directory? Did you follow the instructions in the starts/README.md file? If so, please, stop everything running and start the instructions from the README file again, please.

Cheers!

Reply
Lino M. Avatar

Not Routing, Swal

Reply

Hey @Developper!

Ah sorry you're having trouble! So, to be clear, your error is about how "Swal" is not defined? Are you getting this error after downloading the code from this page? If so, are you using the start/ or finish/ directory?

Let us know and we'll do our best to help :).

Cheers!

Reply

Javascript error in the source code when you are trying to submit a form without filling it :

RepLogApp.js:104 Uncaught (in promise) SyntaxError: Illegal continue statement: no surrounding iteration statement

To avoid it, just change the keyword "continue" in the _mapErrorsToForm method of RepLogApp.js . to "return". It will do the trick.

Reply

Hey tilotiti!

Good find! Thanks for letting us know! We made a recent change, but actually made it in the wrong spot - return is correct (as you mentioned) - we'll change that back. Later in the tutorial when we refactor this to a for loop, THEN continue is the correct choice. Sorry about that - but thanks for the comment!

Cheers!

Reply

Hi. In the example console.log(this, self) area the same but at the top of the script you still calling them method using .bind(this). Is not that the reason?

Reply

Hey micayael!

Ah, good question! At the top of the script, we use the .bind(this) on handleRepLogDelete, handleRowClick and handleNewFormSubmit. But, we do not call it on loadRepLogs, which is what we're calling here.

And actually, to be fully clear, that doesn't matter at all! When we call .then() on .ajax(), that callback is not bound to this. And so, unless you use an arrow function, this will be different in that callback. You could also accomplish this with bind - it would look like this:


$.ajax({
    url: Routing.generate('rep_log_list'),
}).then(function(data) {
    console.log(this, self);
}.bind(this))

In this case - I'm passing a normal (non-arrow) function to .then(). So, it should normally have a different "this". But, because I call .bind(this) ON that callback function, it will retain the proper "this". Overall, this shows that - yes - the arrow function and .bind() solve the same problem. The arrow function is just a little bit nicer :).

I hope that clarifies! Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses Symfony 3. But, since this is a JavaScript tutorial, all the concepts work fine in newer versions of Symfony.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.2.*", // v3.2.14
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.1
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.4.2
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.19
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.0" // v3.2.2
    }
}
userVoice