Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AngularJS In Depth

brianmcd
February 03, 2015

AngularJS In Depth

Given at RVA.js on 2/3/15

brianmcd

February 03, 2015
Tweet

Other Decks in Technology

Transcript

  1. 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
  2. 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
  3. Things We Got Wrong • How to structure the app

    • Lack of unit tests • Not understanding Angular • No visibility into production • No model layer
  4. 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
  5. 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
  6. 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; } }
  7. <library get-results="search(params)" filters="filters" refresh-events="refreshLibrary" search-params="searchParams"> <library-filters show-filter-header="true"> <exemplary-library-filter title="Curriculum Rating">

    </exemplary-library-filter> <curriculum-type-library-filter title="Curriculum Type"> </curriculum-type-library-filter> <grade-library-filter title="Grade Level" grades="grades"> </grade-library-filter> <pacing-library-filter title="Mini-task Pacing"> </pacing-library-filter> </library-filters> <library-results no-results-message="{{ noResultsMessage }}" item-type="LDC curriculum item" filters="filters" sort-by-options="sortByOptions"> <li ng-repeat="result in results"> <result-card curriculum="result" disable-buttons="isSaving" ng-class="{ 'hover-state': !inMinitaskOnlyState }"> </result-card> </li> </library-results> </library>
  8. Directive Benefits • Encapsulation • Small, testable chunks • Template

    + JS bound together explicitly • Explicit interface between components • Can easily refactor components
  9. 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' }); }]);
  10. Don't Use ng-controller <div ng-controller="AppCtrl"> <div class="age-display">{{ age }}</div> <div

    ng-controller="AgeInputCtrl"> <input ng-model="age"> <button ng-click="save()">Save</button> </div> </div>
  11. 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.
  12. Things We Got Wrong • How to structure the app

    • Lack of unit tests • Not understanding Angular • No visibility into production • No model layer
  13. 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
  14. 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())
  15. Input: <input ng-model="age"> Age: <span class="age">{{ age }}</span> <button class="reset-age"

    ng-click="reset()"> </button> 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? <simple-age-input></simple-age-input> Usage:
  16. A Half-Test describe('simpleAgeInput', function () { var elem, scope; beforeEach(function

    () { module('app', 'code/simpleAgeInput.tpl.html'); inject(function ($compile, $rootScope) { elem = $compile('<simple-age-input></simple-age-input>')($rootScope); }); scope = elem.isolateScope(); }); it('resets the age', function () { scope.age = 50; scope.reset(); expect(scope.age).toBe(100); }); });
  17. 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
  18. A Full Test describe('simpleAgeInput', function () { var elem; beforeEach(function

    () { module('app'); inject(function ($compile, $rootScope) { elem = $compile('<simple-age-input></simple-age-input>')($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'); }); });
  19. 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()
  20. Things We Got Wrong • How to structure the app

    • Lack of unit tests • Not understanding Angular • No visibility into production • No model layer
  21. The $digest Loop • AKA dirty checking, the digest cycle,

    magic • Enables data binding • Not actually magic - very straightforward
  22. angular. module('rvajs'). controller('Example', function ($scope) { $scope.age = 100; $scope.$watch('age',

    function (age) { console.log(age); }); }); <input ng-model="age"> <div>{{ age }}</div> JavaScript: Template:
  23. 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".
  24. 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.
  25. 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
  26. • 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 <input ng-model="age"> <div>{{ age }}</div> Template: Behind the Scenes
  27. 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
  28. 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)
  29. Example Value 1: <input ng-model="value1"> <div ng-if="true"> Value 2: <input

    ng-model="value2"> </div> <div class="values"> <div>Value 1: {{ value1 }}</div> <div>Value 2: {{ value2 }}</div> </div> scope.value1 = 'this'; scope.value2 = 'that';
  30. Fixed Example Value 1: <input ng-model="values.value1"> <div ng-if="true"> Value 2:

    <input ng-model="values.value2"> </div> <div class="values"> <div>Value 1: {{ values.value1 }}</div> <div>Value 2: {{ values.value2 }}</div> </div> scope.values = { value1: 'this', value2: 'that' };
  31. Things We Got Wrong • How to structure the app

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

    • Lack of unit tests • Not understanding Angular • No visibility into production • No model layer
  33. 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
  34. Our Solution • We rolled our own • Based on

    JavaScript classes and inheritance • Supports collections and collection methods • Will be open-sourced soon
  35. Things We Got Wrong • How to structure the app

    • Lack of unit tests • Not understanding Angular • No visibility into production • No model layer
  36. Performance • One-time binds (new in 1.3) • <div>{{ ::variable

    }}</div> • 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
  37. ng-strict-di • Add this to your ng-app element: • <html

    ng-app="app" ng-strict-di> • Throws an error if you're missing a DI annotation • Added in 1.3
  38. Upgrading Angular • It's worth it to stay up to

    date • Upgrading is usually easy • Always read the changelog • Another reason to have tests
  39. Final Thoughts • Embrace directives • Test your code •

    There's no such thing as magic • Don't be afraid to dig into the Angular source