If you’re reasonably up-to-date with front-end app development then there’s a good chance you’ve heard the buzz flying around about ECMAScript 6. For those unfamiliar, ECMAScript 6, also known as Harmony, is the next major version of JavaScript programming language. In this post, I’ll utilize new ECMAScript 6 features to reduce and improve readability of code in a sample AngularJS application.
Our sample application is a simple baseball card auction app that serves as a platform for users to sell rather valuable baseball cards and consists of three Controllers (BaseballCardsCtrl, BaseModalCtrl, and ConfirmModalCtrl) and three Services (UserService, CardService, and NotificationService).
Block-scoped bindings and arrow functions
The very first thing our application needs to do is fetch the current user’s profile, so we’ll start by taking a look at the getUserInfo
function of our UserService which is responsible for retrieving the user’s information.
function getUserInfo() { if (user) { return $q.when(user); } else { let handleSuccess = (response) => { user = response.data; return user; }; let handleError = (error) => { NotificationService.logError('Unable to retrieve user info', error); return null; }; return $http.get('/dist/data/get_user_info_response.json').then(handleSuccess, handleError); } }
This function uses two of the new features of ECMAScript 6, namely block-scoped bindings, and arrow, or lambda functions. The handleSuccess
and handleError
functions above are declared using the let keyword, which restricts them to only being accessible by code in the same block; in this case, the else block of our if-else statement. This increases the overall readability of our code by moving variable or function declarations and assignments into the block where they’re intended to be used. Both handlers make use of arrow functions which provide a shorthand, expressive syntax for defining functions. In addition to providing a more compact syntax for defining functions, arrow functions also lexically bind the “this” keyword value, eliminating the need to manually bind object context when registering event handlers and such.
Generators and paused execution
Next, let’s take a look at an excerpt from our BaseballCardsCtrl that uses the UserService’s getUserInfo
and CardService’s getCards
methods to retrieve and store a reference to the user’s profile and baseball card data.
var it; initialize(); function initialize() { vm.notifications = NotificationService.getNotifications(); it = fetchData(); it.next(); } function *fetchData() { vm.user = yield getUserInfo(); vm.cards = yield getCards(vm.user); } function getUserInfo() { UserService.getUserInfo().then((user) => { it.next(user); }); } function getCards(user) { CardService.getCards(user).then((cards) => { it.next(cards); }); }
The most interesting thing to note about the above code is the *fetchData
function declaration. The function name is prefixed with an asterisk which is a new, special naming convention that results in the function returning a Generator object, another new feature of ECMAScript 6.
Generators are special Iterators that can pause their execution when the yield keyword is encountered. The ability to pause function execution allows us to control the order of asynchronous operations without need to use a series of nested callbacks. In our initialize function above, we store a reference to the Generator object on which we immediately invoke it’s next method to instruct *fetchData
to begin executing its function body. The *fetchData
function calls getUserInfo
then pauses due to the presence of the yield keyword. The getUserInfo
function is responsible for registering a callback function to be executed once the user’s information is retrieved from the server. Once the user’s information is retrieved, the callback invokes the Generator’s next function with the user data, which is returned by the first yield statement in *fetchData
. The *fetchData
function then releases the pause allowing execution to continue and the getCards
function to be invoked with the necessary user data.
Object destructuring and string templates
Now that our application has been wired up to retrieve the necessary user and baseball card data, it needs a way to allow our user to actually sell his cards. The following snippet is from our CardService.
function sellCard(card, index) { return $http.get('/dist/data/sell_card_response.json').then(handleSuccess, handleError); function handleError(error) { NotificationService.logError('Unable to sell card', error); return null; } function handleSuccess() { var { year, firstName, lastName, value } = card, dollars = $filter('currency')(value); cards.splice(index, 1); UserService.updateBalance(value); NotificationService.logSuccess(`Sold ${year} ${firstName} ${lastName} for ${dollars}`); return card; } }
The majority of this code looks similar to what you’ve seen in other AngularJS applications with one exception, the first line within the nested handleSuccess method. This particular line uses what’s called Object Destructuring, another one of our new ECMAScript 6 features. Object Destructuring provides a shorthand syntax for declaring one or more variables with their values being initialized as property values of a target object. This is only a very basic example of how one might use Object Destructuring to consolidate multiple variable declarations into a single statement; it does support more complex mappings to nested object properties as well.
Another difference you may have noticed is our use of a string template to build a success message to be used as a “successful sale” notification. Traditionally, this would have been accomplished by concatenating multiple string literals with variables, but now we can achieve the same result using much more concise, readable syntax using variable interpolation with template strings.
Classes and prototypal inheritance
One requirement of our application is to prompt our users to confirm that they would like to proceed, after clicking a button to sell a particular baseball card. For this demo we use the UI-Bootstrap modal for our confirmation prompt. In real world applications there is often the need to utilize many different modals that implement a common subset of functions to handle repetitive tasks such as closing the modal when the user clicks a Cancel button. Implementing these functions for each modal leads to redundant code. Fortunately, ECMAScript 6 introduces a new way to approach prototypal inheritance in JavaScript using a more concise, readable syntax.
function BaseModalCtrl($modalInstance) { 'use strict'; var vm = this; vm.dismiss = $modalInstance.dismiss; vm.close = $modalInstance.close; }
First we define an ordinary constructor function that creates dismiss and close members that reference the $modalInstance
service’s dismiss and close methods. Admittedly, there’s nothing new or interesting to see here but in our next code excerpt, we will see how we can easily extend this functionality when we create our ConfirmModalCtrl.
class ConfirmModalCtrl extends BaseModalCtrl { constructor(data, $modalInstance) { super($modalInstance); this.data = data; } }
While this feature isn’t a new concept in JavaScript language, it does provide syntactic sugar, and achieve prototypal inheritance using a more concise, intuitive syntax. ECMAScript 6 classes and inheritance offer a standardized way to define JavaScript class constructors, public and private members, and make superclass methods calls. Of course, this is all possible today using native ECMAScript 5 code, but the syntax is not as elegant or intuitive. We now have an easy, readable way to extend functionality and eliminate code redundancy in our JavaScript classes.
Structure through modules
Lastly, let’s learn how to import various application components and register them with AngularJS. Historically, JavaScript has lacked a native way to structure applications and manage component dependencies. However while AngularJS does have its own dependency injection mechanism, it does not support importing external dependencies and requires components be available from within the current scope before registering with Angular.
ECMAScript aims to provide a native, standardized way to handle importing external dependencies with the introduction of modules. Lets take a look at how we can utilize the new module syntax to import external file dependencies and register our different components with Angular.
import BaseballCardsCtrl from './controllers/baseball_cards.ctrl.js'; import ConfirmModalCtrl from './controllers/confirm_modal.ctrl.js'; import UserService from './services/user.service.js'; import CardService from './services/card.service.js'; import NotificationService from './services/notification.service.js'; angular.module('demo', ['ui.bootstrap']) .controller('ConfirmModalCtrl', ConfirmModalCtrl) .controller('BaseballCardsCtrl', BaseballCardsCtrl) .service('UserService', UserService) .service('CardService', CardService) .service('NotificationService', NotificationService);
As you can see in our above example, ECMAScript 6 modules introduce the new import keyword, allowing you to reference application components defined in external files. Here, we import each of our AngularJS application components (BaseballCardsCtrl, ConfirmModalCtrl, UserService, CardService, and NotificationService) and register them with our demo application. Each of our components uses an export statement, another new keyword in ECMAScript 6, to selectively make internal components available for import and use in other files..
export default NotificationService; function NotificationService($log, $timeout) { ... }
The import and export statements support different syntax giving you flexibility over how components are referenced throughout your application. Note: We’ve only used the most basic, as it fulfills our application’s current needs.
Conclusion
ECMAScript 6 is still in draft mode and its specification isn’t expected to be officially published until later this year. For this reason, modern browsers currently have varying or incomplete support for many of the new language features. Fortunately though, there are quite a few tools to integrate support for ECMAScript 6 into your workflows and translate the code to ECMAScript 5 compatible syntax. As the release of the new ECMAScript 6 specification approaches, its new features are being integrated into more and more applications.
JavaScript is one of the most popular and widely used programming languages today; it’s not going anywhere any time soon and as it continues to evolve, so should our applications to harness it’s full potential and expressiveness. Our sample application uses one subset of the new features available in ECMAScript 6; there’s a lot more to it than what we’ve seen in this short example. For more complete information on ECMAScript 6 and how to integrate support for it in your workflow, check out the following resources.
Application
- Demo: http://embed.plnkr.co/dLK1ruytvNSljXz5L3CV/preview
- Repository: https://github.com/rjw209/angular-es6-demo
Resources