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