Slide 1

Slide 1 text

Angular Brian McDaniel 2-3-15 RVA.js

Slide 2

Slide 2 text

[email protected] github.com/brianmcd

Slide 3

Slide 3 text

Background • Started working with Angular in March 2013 • Joined current project in October 2013 • When I joined: • ~2000 LOC • ~10 unit tests • Now: • ~32000 LOC (JS, HTML, CSS) • ~34000 LOC (unit tests) • >3600 unit tests

Slide 4

Slide 4 text

Disclaimer • Not an Angular introduction - plenty of those online • This is information I wish someone had told me 1 month after I started working with Angular

Slide 5

Slide 5 text

Things We Got Wrong • How to structure the app • Lack of unit tests • Not understanding Angular • No visibility into production • No model layer

Slide 6

Slide 6 text

Organizing Your App Logic

Slide 7

Slide 7 text

The Answer: Directives

Slide 8

Slide 8 text

Directives • A way of creating components • A way of encapsulating logic • Tie together a chunk of HTML and its behaviors • Great way of factoring out code • Allow you to reason about your application hierarchically

Slide 9

Slide 9 text

Directive Tips • Do NOT have to be reusable • Most of your time writing Angular should be spent writing directives (and tests) • When creating directives (E): • Isolate scope • Restrict to element

Slide 10

Slide 10 text

Styling Directives • Use a CSS preprocessor (LESS, SASS) • Scope your styles to the directive element example-directive { display: block; cursor: pointer; a { font-size: 20px; font-weight: bold; } }

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

The HTML

Slide 13

Slide 13 text

  • Slide 14

    Slide 14 text

    Directive Benefits • Encapsulation • Small, testable chunks • Template + JS bound together explicitly • Explicit interface between components • Can easily refactor components

    Slide 15

    Slide 15 text

    Routes • The one place you need controllers • Pages -> routes • Components within a page -> directives • Use ui-router angular.module('login'). config(['$stateProvider', function ($stateProvider) { $stateProvider.state('login', { parent: 'root', url: '/login?invited_user_code?afterLogin', templateUrl: 'login/login.tpl.html', controller: 'LoginCtrl', title: 'LDC CoreTools - Login' }); }]);

    Slide 16

    Slide 16 text

    Don't Use ng-controller
    {{ age }}
    Save

    Slide 17

    Slide 17 text

    Issues with ng-controller • No interface definition between controllers • Variables are prototypally inherited • Re-arrange template -> broken code • Controllers are tied to their templates, but nothing enforces that.

    Slide 18

    Slide 18 text

    Things We Got Wrong • How to structure the app • Lack of unit tests • Not understanding Angular • No visibility into production • No model layer

    Slide 19

    Slide 19 text

    Testing

    Slide 20

    Slide 20 text

    Why test? • Sensing mechanism for changes to your code • Makes upgrading Angular easy (tests will break) • Gives you some confidence that your code works, and will continue to do so. • Angular makes it easy

    Slide 21

    Slide 21 text

    Testing in Angular • Plenty of guides on the mechanics of testing a controller, directive, service, etc. • Very easy to mock dependencies var $modalInstance = jasmine.createSpyObj('$modalInstance', [ 'close', 'dismiss' ]); module(function ($provide) { $provide.value('$modalInstance', $modalInstance); }); ... expect($modalInstance.close).toHaveBeenCalled() • Rarely need async tests ($timeout.flush(), $httpBackend.flush())

    Slide 22

    Slide 22 text

    Input: Age: {{ age }} directive('simpleAgeInput', function () { return { restrict: 'E', templateUrl: 'code/simpleAgeInput.tpl.html', scope: {}, link: function (scope) { scope.age = 100; scope.reset = function () { scope.age = 100; }; } }; } Template: JavaScript: How would you test this? Usage:

    Slide 23

    Slide 23 text

    A Half-Test describe('simpleAgeInput', function () { var elem, scope; beforeEach(function () { module('app', 'code/simpleAgeInput.tpl.html'); inject(function ($compile, $rootScope) { elem = $compile('')($rootScope); }); scope = elem.isolateScope(); }); it('resets the age', function () { scope.age = 50; scope.reset(); expect(scope.age).toBe(100); }); });

    Slide 24

    Slide 24 text

    Uni-gration Tests • Somewhere between a unit test and an integration test. • Process: • Instantiate your directive • Interact with it like a user would • Don't: • Directly call scope methods/check scope variables • Assume that your template logic is correct

    Slide 25

    Slide 25 text

    A Full Test describe('simpleAgeInput', function () { var elem; beforeEach(function () { module('app'); inject(function ($compile, $rootScope) { elem = $compile('')($rootScope); }); }); it('resets the age', function () { elem.find('input').val('50').change(); expect(elem.find('.age').text()).toBe('50'); elem.find('.reset-age').click(); expect(elem.find('.age').text()).toBe('100'); }); });

    Slide 26

    Slide 26 text

    Helpful Libraries • Check out jasmine-jquery (https://github.com/velesin/jasmine-jquery) • .toBeVisible(), .toContainText(), .toHaveClass() • Check out angular-test-helpers (https://github.com/brianmcd/angular- test-helpers) • createDirective(), $rootScopeDigest()

    Slide 27

    Slide 27 text

    Things We Got Wrong • How to structure the app • Lack of unit tests • Not understanding Angular • No visibility into production • No model layer

    Slide 28

    Slide 28 text

    Understanding Angular

    Slide 29

    Slide 29 text

    2 Important Concepts • The $digest loop • Scopes

    Slide 30

    Slide 30 text

    The $digest Loop • AKA dirty checking, the digest cycle, magic • Enables data binding • Not actually magic - very straightforward

    Slide 31

    Slide 31 text

    angular. module('rvajs'). controller('Example', function ($scope) { $scope.age = 100; $scope.$watch('age', function (age) { console.log(age); }); });
    {{ age }}
    JavaScript: Template:

    Slide 32

    Slide 32 text

    How $digest Works • Maintains a list of "watched" expressions (bindings usually) • For each watched expression, it tracks the current value • The loop: • Calculate the current value of each watched expression • Compare current value to last value • If no change, don't do anything for this expression. • If change, notify the "watchers".

    Slide 33

    Slide 33 text

    When does the loop run? • Angular can't magically know when to run the $digest loop • JavaScript variables can only change at well-defined points: • User action (event) • Timer • Network request • When these things happen, Angular needs to be told to run the $digest loop to see if things changed.

    Slide 34

    Slide 34 text

    scope.$apply() • Tells Angular "something could have changed" • Usually done for you, but always must be done • When can variables change? • User action (event) - ng-click, ng-keydown, etc • Timer - $timeout, $interval • Network request - $http • These Angular wrappers are calling scope.$apply for you

    Slide 35

    Slide 35 text

    ng-click Implementation • Not magic. element.on('click', function(event, touchend) { scope.$apply(function() { clickHandler(scope, {$event: (touchend || event)}); }); });

    Slide 36

    Slide 36 text

    • input directive registers keydown listener • interpolation binding registers a watcher on age • On keydown: • input directive calls scope.$apply('age = "1";') • $digest loop starts • $digest loop notices that age has changed • $digest loop notifies age watchers • interpolation directive updates the view
    {{ age }}
    Template: Behind the Scenes

    Slide 37

    Slide 37 text

    Scopes

    Slide 38

    Slide 38 text

    Scopes • The execution context for your templates • Scopes are tied to DOM nodes • Therefore, the scope hierarchy forms a tree • Scopes form a prototypal inheritance hierarchy

    Slide 39

    Slide 39 text

    $rootScope AppCtrl's Scope AnotherCtrl's Scope Scope Inheritance

    Slide 40

    Slide 40 text

    Accidental Scopes • Some built-in directives create new scopes in the prototype chain: • ng-repeat • ng-if • ng-switch • ng-view • ng-include (never use this anyway) • ng-controller (also don't use this)

    Slide 41

    Slide 41 text

    Example Value 1:
    Value 2:
    Value 1: {{ value1 }}
    Value 2: {{ value2 }}
    scope.value1 = 'this'; scope.value2 = 'that';

    Slide 42

    Slide 42 text

    Fixed Example Value 1:
    Value 2:
    Value 1: {{ values.value1 }}
    Value 2: {{ values.value2 }}
    scope.values = { value1: 'this', value2: 'that' };

    Slide 43

    Slide 43 text

    Things We Got Wrong • How to structure the app • Lack of unit tests • Not understanding Angular • No visibility into production • No model layer

    Slide 44

    Slide 44 text

    Rollbar

    Slide 45

    Slide 45 text

    Things We Got Wrong • How to structure the app • Lack of unit tests • Not understanding Angular • No visibility into production • No model layer

    Slide 46

    Slide 46 text

    The Model Layer

    Slide 47

    Slide 47 text

    Anti-patterns • Static services, initialized on page load • C-style services that are groups of methods • function doThing(obj, param1, param2) • POJO data objects (structs) • No support for inheritance • No way of coupling data with the methods that operate on it

    Slide 48

    Slide 48 text

    $resource • Built into Angular • Doesn't support inheritance • Doesn't support collection methods

    Slide 49

    Slide 49 text

    Our Solution • We rolled our own • Based on JavaScript classes and inheritance • Supports collections and collection methods • Will be open-sourced soon

    Slide 50

    Slide 50 text

    Things We Got Wrong • How to structure the app • Lack of unit tests • Not understanding Angular • No visibility into production • No model layer

    Slide 51

    Slide 51 text

    Bonus: Tips and Tricks

    Slide 52

    Slide 52 text

    Console Debugging • angular.element(document.body).injector().get('anyService') • angular.element($0).scope() / angular.element($0).isolateScope()

    Slide 53

    Slide 53 text

    Performance • One-time binds (new in 1.3) •
    {{ ::variable }}
    • ng-show vs. ng-if • ng-show: hides with CSS • ng-if: removes from DOM • Don't touch the DOM in a watcher • In general, has not been a problem for us

    Slide 54

    Slide 54 text

    ng-strict-di • Add this to your ng-app element: • • Throws an error if you're missing a DI annotation • Added in 1.3

    Slide 55

    Slide 55 text

    Upgrading Angular • It's worth it to stay up to date • Upgrading is usually easy • Always read the changelog • Another reason to have tests

    Slide 56

    Slide 56 text

    Final Thoughts • Embrace directives • Test your code • There's no such thing as magic • Don't be afraid to dig into the Angular source

    Slide 57

    Slide 57 text

    The End