Refactoring Angular Directives from ES5 to ES2015

Recently I built a small app, Meteo, that was a clone of Currently, an app I use everyday. I talked about it briefly in my last post but wanted to discuss it more here.

Initially I built the app on the Meteor platform as Meteor serves as a simple way to set up a server, have a build process, and deploy the app. However, I found the ease of Meteor to be both a gift and a curse. There are several technologies I’ve wanted to explore in more detail so after building the app with Meteor I decided to rebuild it to work on these pieces I’ve wanted to learn including:

  • Node/Express
  • Gulp
  • ES2015
  • Babel
  • Browserify

In this post I’m going to focus on ES2015, and how I refactored my two angular directives to take advantage of this new syntax.

The first directive I refactored was the dateTimeDirective that displays the current time and date of a user’s local machine.

The initial directive writtin in ES5 was:

(function() {
  'use strict';

  function dateTime( $timeout ) {

    function dateTimeCtrl() {

      var vm = this;

      vm.todaysDate = moment().format('dddd MMMM DD YYYY');

      var updateTime = function(){

        var now = moment().format('hh:mm:ss A');

        vm.currentTime = now;
        $timeout(updateTime, 1000);

      };
      
      updateTime();

    }

    return {
      restrict: 'EA',
      controller: dateTimeCtrl,
      controllerAs: 'vm',
      scope: {},
      templateUrl: 'components/date-time/date-time.html'
    };

  }

  angular
    .module('DateTimeDirective', [])
    .directive('dateTime', ['$timeout', dateTime]);

})();

Taking advantage of ES2015’s new let and const variables I was able to change the var declarations I had to let which now creates a block scope for the variables.

I then changed my function declarations to include the fat arrow syntax. The benefit of this is that I can remove setting this equal to another value, in my case vm, since this is now lexically bound within the function.

With these changes made my dateTimeDirective directive now looked like:

import angular from 'angular';
import moment from 'moment';

(function() {
  'use strict';

  function dateTime( $timeout ) {

    function dateTimeCtrl() {

      this.todaysDate = moment().format('dddd MMMM DD YYYY');

      let updateTime = () => {

        let now = moment().format('hh:mm:ss A');

        this.currentTime = now;
        $timeout(updateTime, 1000);

      };
      
      updateTime();

    }

    return {
      restrict: 'EA',
      controller: dateTimeCtrl,
      controllerAs: 'vm',
      scope: {},
      templateUrl: 'components/date-time/date-time.html'
    };

  }

  angular
    .module('DateTimeDirective', [])
    .directive('dateTime', ['$timeout', dateTime]);

})();

With a similar structure I implemented the same changes on the weatherForecastDirective.

// weatherForecastDirective ES5

(function() {
  'use strict';

  function dateTime( $timeout, $http ) {

    function weatherForecastCtrl() {

      var vm = this;

      vm.Math = window.Math;

      navigator.geolocation.getCurrentPosition(function(response) {

        $http.get('/address/' + response.coords.latitude + '/' + response.coords.longitude).then(function(res) {

          var addressData = JSON.parse(res.data.body);

          vm.address = addressData.results[0].formatted_address;

        })

        $http.get('/weather/' + response.coords.latitude + '/' + response.coords.longitude).then(function(res) {

          var data = JSON.parse(res.data.body);

          vm.hourlyForecast = data.hourly;

          vm.dailyForecast = data.daily;

        })

      });

    }

    return {
      restrict: 'EA',
      controller: weatherForecastCtrl,
      controllerAs: 'vm',
      scope: {},
      templateUrl: 'components/weather-forecast/weather-forecast.html'
    };

  }

  angular
    .module('WeatherForecastDirective', ['WeatherForecastFilters'])
    .directive('weatherForecast', ['$timeout', '$http', dateTime]);

})();

weatherForecastDirective ES2015

import angular from 'angular';

(function() {
  'use strict';

  function dateTime( $http ) {

    function weatherForecastCtrl() {

      this.Math = window.Math;

      navigator.geolocation.getCurrentPosition( (response) => {

        let addressAPICall = `/address/${response.coords.latitude}/${response.coords.longitude}`;

        let weatherAPICall = `/weather/${response.coords.latitude}/${response.coords.longitude}`;

        $http.get(addressAPICall).then( (res) => {

          const addressData = JSON.parse(res.data.body);

          this.address = addressData.results[0].formatted_address;

        })

        $http.get(weatherAPICall).then( (res) => {

          const data = JSON.parse(res.data.body);

          this.hourlyForecast = data.hourly;

          this.dailyForecast = data.daily;

        })

      });

    }

    return {
      restrict: 'EA',
      controller: weatherForecastCtrl,
      controllerAs: 'vm',
      scope: {},
      templateUrl: 'components/weather-forecast/weather-forecast.html'
    };

  }

  angular
    .module('WeatherForecastDirective', ['WeatherForecastFilters'])
    .directive('weatherForecast', ['$http', dateTime]);

})();

Now that I had refactored the code to utilize this new syntax, the next step was making sure my build process was properly compiling the code back to ES5 for the browser to render properly.

While I won’t go through my entire Gulp file here, the following is what I’m using to take all my client side code, browserify the dependencies, then run Babel, and finally have it concatanated to one final file.

'use strict';

import gulp           from 'gulp';
import babelify       from 'babelify';
import uglify         from 'gulp-uglify';
import maps           from 'gulp-sourcemaps';
import glob           from 'glob';
import browserify     from 'browserify';
import buffer         from 'vinyl-buffer'
import source         from 'vinyl-source-stream';

gulp.task('browserify', () => {

  let clientFiles = glob.sync(
        'client/js/**/*.js'
      );
  let templateFiles = glob.sync('client/public/*.js');
  let filesToConcat = clientFiles.concat(templateFiles);

  return browserify({entries: filesToConcat, debug: true})
      .transform(babelify)
      .bundle()
      .pipe(source('app.js'))
      .pipe(buffer())
      .pipe(maps.init({loadMaps: true}))
      .pipe(uglify())
      .pipe(maps.write('./'))
      .pipe(gulp.dest('client/bundle'));
})

Once the browserify task is ran the client side code is transpiled back to ES5 and ready to be viewed in the browser.

To see all the code for this project please visit: Code

To visit the site in production please visit: Météo