Frontend Development at

David Tang / JS LA 2015

About Me

  • UI Architect at Verizon Digital Media Services
  • Teach web development classes at USC for the Information Technology Program (ITP)
  • Passionate about testing the frontend

Overview

  • Our technology stack and tooling
  • How we use a component architecture in Angular to make more maintainable and testable apps

Istanbul

APIs

app.controller('CatFormController', function(cat, Kitten, $log, CatValidator, Food, Color, $location) {
    var vm = this;

    init();

    function init() {
        Kitten.all({ cat: cat }).then(function(kittens) {
            vm.kittens = kittens;
        });

        Food.all().then(function(foods) {
            vm.foods = foods;
        });

        Color.all().then(function(colors) {
            vm.colors = colors;
        });
        
    }

    vm.cancel = function() { /* implementation */ };
    
    vm.reset = function() { /* implementation */ };
    
    vm.save = function() {
        // validation
        // create cat if valid
    };

    vm.editKitten = function() { /* implementation */ };

    vm.createKitten = function() { /* implementation */ };
});

Monolithic controllers that are hard to understand

Difficult to unit test because there are too many dependencies

Tests become harder to read/write because of mocking

Tests are abandoned or never started

Component based architecture

What is a component in Angular?

"A directive that is restricted to element names with an isolated scope that uses transclusion."

Godfrey Chan

// action-button.js
app.directive('actionButton', function() {
    return {
        restrict: 'E',
        templateUrl: 'action-button.html',
	scope: {
	    action: '&',
	    value: '@',
	    processingValue: '@'
	},
    	link: function($scope, $el, attrs) {
            // maybe some DOM manipulation here?

    	    $scope.displayLabel = $scope.value; 
    	    $scope.runAction = function() {
	        $scope.processing = true;
	    	$scope.displayLabel = $scope.processingValue; 
	    	$scope.action().finally(function() {
	    	    $scope.processing = false;
	    	    $scope.displayLabel = $scope.value; 
	    	});
    	    };
    	}
    };
});
<action-button 
    value="Create" 
    processing-value="Processing..." 
    action="vm.create()"></action-button>
<!-- action-button.html -->
<button 
    ng-disabled="processing" 
    ng-click="runAction()">{{displayLabel}}</button>

DOM dependent (slow)

Requires having the template in the test

ng-html2js, $httpBackend

app
    .directive('actionButton', function() {
	return {
            restrict: 'E',
            templateUrl: 'action-button.html',
            scope: {
                action: '&',
	        value: '@',
		processingValue: '@'
            },
            controller: 'ActionButtonController',
            controllerAs: 'vm',
	    bindToController: true, // Angular 1.3
            link: function(scope, el, attrs, controller) {
                // custom DOM manipulation here
            }
        };
    })
    .controller('ActionButtonController', function() {
        var vm = this;
        vm.displayLabel = vm.value;
        vm.runAction = function() {
            vm.processing = true;
            vm.displayLabel = vm.processingValue;

            vm.action().finally(function() {
	        vm.processing = false;
                vm.displayLabel = vm.value;
	    });
        };
    });

Separate logic from DOM manipulation that pairs with the directive 

describe('ActionButtonController', function() {
    var $controller, $timeout;

    beforeEach(module('kittens'));
    beforeEach(inject(function(_$controller_, _$timeout_) {
      $controller = _$controller_;
      $timeout = _$timeout_;
    }));

    it('should call the action when runAction is called', function() {
        var actionSpy = jasmine.createSpy().and.returnValue($timeout(0));
        var controller = $controller('ActionButtonController', {}, {
          action: actionSpy
        });
        controller.runAction();
        expect(actionSpy).toHaveBeenCalled();
    });
});

Test directive logic independent of the directive

No longer dependent on the DOM

Don't need templates in your tests

Summary

  • VDMS supports a variety of backends
  • We use a component based model in Angular similar to Ember and React which helps with maintainability and testability
  • We are hiring UI and JavaScript Developers

Thank You!

Questions?

dtang@verizon.com
@skaterdav85

Unused

Requires having the template in the test

describe('<action-button> directive', function() {
  var scope, $compile, $timeout;

  beforeEach(module('kittens'));
  beforeEach(inject(function($rootScope, _$compile_, _$timeout_) {
      scope = $rootScope.$new();
      $compile = _$compile_;
      $timeout = _$timeout_;
  }));

  it('should call the action when runAction is called', function() {
      var element = '<action-button value="Create" processing-value="Processing..." action="create()"></action-button>';
      scope.create = jasmine.createSpy().and.returnValue($timeout(0));
      element = $compile(element)(scope);
      element.isolateScope().runAction();
      expect(scope.create).toHaveBeenCalled();
  });
});

ng-html2js, $httpBackend

DOM dependent (slow)

Instead, separate logic from DOM manipulation into a controller that your directive references 

You could use nested controllers with

$parent.$parent.$parent

Makes testing a little more difficult

Not obvious what data is accessible in the controller

ng-controller

How we build Angular Apps at Verizon Digital Media Services

By David Tang

How we build Angular Apps at Verizon Digital Media Services

JS LA 2015

  • 812