Technology

A Week's Small Victories: Angular, Equality and Testing

By Ian McNally

Our work weeks are made up of victories both big and small. What really gets me writing is the small. The a-ha! moments. The high fives (internal ones count too). The green builds. The git pushes.

I spent my week creating dynamic forms and lists in Angular. I made them, fought with them, tested them, and came out the other side in one piece. Here's my story:

 

 

Writing and testing custom form validators

Custom form validators are a powerful feature in Angular.

Say I've got a form, and an input for a name. I want the name to be unique from a list of pre-existing names (shout out to Beatles fans):

form.html

<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">form</span> <span class="cm-attribute">name</span>=<span class="cm-string">"nameform"</span><span class="cm-tag cm-bracket">&gt;</span>
<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">input</span> <span class="cm-attribute">name</span>=<span class="cm-string">"nameinput"</span> <span class="cm-attribute">ng-model</span>=<span class="cm-string">"fifthMember"</span> <span class="cm-attribute">unique-name-validator</span> <span class="cm-attribute">current-names</span>=<span class="cm-string">"theBeatles"</span> <span class="cm-tag cm-bracket">/&gt;</span>
<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">form</span><span class="cm-tag cm-bracket">&gt;</span>

controller.js

<span class="cm-variable">$scope</span>.<span class="cm-property">theBeatles</span> <span class="cm-operator">=</span> [<span class="cm-string">'John'</span>, <span class="cm-string">'George'</span>, <span class="cm-string">'Paul'</span>, <span class="cm-string">'Ringo'</span>];

 

Creating a validator

Take a look at the attribute unique-name-validator and current-names. That's a reference to the custom validator, which wraps itself in a directive:

<span class="cm-variable">angular</span>.<span class="cm-property">module</span>(<span class="cm-string">'addABeatle'</span>).<span class="cm-property">directive</span>(<span class="cm-string">'uniqueNameValidator'</span>, <span class="cm-keyword">function</span>(){
    <span class="cm-keyword">return</span> {
        <span class="cm-property">require</span>: <span class="cm-string">'ngModel'</span>,
        <span class="cm-property">scope</span>: {
            <span class="cm-property">currentNames</span>: <span class="cm-string">'='</span>
        },
        <span class="cm-property">link</span>: <span class="cm-keyword">function</span>(<span class="cm-def">$scope</span>, <span class="cm-def">_e</span>, <span class="cm-def">_a</span>, <span class="cm-def">modelController</span>){
            <span class="cm-variable-2">modelController</span>.<span class="cm-property">$validators</span>.<span class="cm-property">uniqueName</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">newName</span>){
                <span class="cm-keyword">return</span> <span class="cm-operator">!</span><span class="cm-variable">_</span>.<span class="cm-property">contains</span>(<span class="cm-variable-2">$scope</span>.<span class="cm-property">currentNames</span>, <span class="cm-variable-2">newName</span>);
            };
        }
    };
});

Requiring ngModel gives you access to the model controller that handles the ng-model="fifthBeatles". On that controller, a validator is added. It is simply a function that returns true if the input is valid, false if it's invalid.

From there, I use the current-names attribute to test if the name is unique (lodash used here). It gets attached to $scope when I isolate it on the directive.

 

Testing

Testing, after some setup work, is pretty straight forward.

You create an element and $compile it. To get a reference to the input, I leveraged Angular's behavior of attach forms onto the current $scope.

<span class="cm-variable">describe</span>(<span class="cm-string">'directive: uniqueNameValidator'</span>, <span class="cm-keyword">function</span>(){
    <span class="cm-keyword">var</span> <span class="cm-def">$scope</span>, <span class="cm-def">input</span>;

    <span class="cm-variable">beforeEach</span>(<span class="cm-variable">inject</span>(<span class="cm-keyword">function</span>(<span class="cm-def">$compile</span>, <span class="cm-def">$rootScope</span>){
        <span class="cm-variable-2">$scope</span> <span class="cm-operator">=</span> <span class="cm-variable-2">$rootScope</span>.<span class="cm-property">$new</span>();
        <span class="cm-variable-2">$scope</span>.<span class="cm-property">theBeatles</span> <span class="cm-operator">=</span> [<span class="cm-string">'John'</span>, <span class="cm-string">'George'</span>, <span class="cm-string">'Paul'</span>, <span class="cm-string">'Ringo'</span>];
        <span class="cm-keyword">var</span> <span class="cm-def">element</span> <span class="cm-operator">=</span> <span class="cm-string">'&lt;form name="nameform"&gt;&lt;input name="nameinput" ng-model="fifthMember" unique-name-validator current-names="theBeatles" /&gt;&lt;/form&gt;'</span>;
        <span class="cm-variable-2">$compile</span>(<span class="cm-variable-2">element</span>)(<span class="cm-variable-2">$scope</span>);
        <span class="cm-variable-2">input</span> <span class="cm-operator">=</span> <span class="cm-variable-2">$scope</span>.<span class="cm-property">nameform</span>.<span class="cm-property">nameinput</span>;
    }));

Then the tests become a matter of setting the view's value, and testing for $valid or $invalid:

<span class="cm-variable">it</span>(<span class="cm-string">'marks a unique name $valid'</span>, <span class="cm-keyword">function</span>(){
    <span class="cm-variable">input</span>.<span class="cm-property">$setViewValue</span>(<span class="cm-string">'Pete Best'</span>); <span class="cm-comment">// Beatles dropout!</span>
    <span class="cm-variable">$scope</span>.<span class="cm-property">$digest</span>();
    <span class="cm-variable">expect</span>(<span class="cm-variable">input</span>.<span class="cm-property">$valid</span>).<span class="cm-property">toBe</span>(<span class="cm-atom">true</span>);
});

<span class="cm-variable">it</span>(<span class="cm-string">'marks a not unique name $invalid'</span>, <span class="cm-keyword">function</span>(){
    <span class="cm-variable">input</span>.<span class="cm-property">$setViewValue</span>(<span class="cm-string">'Paul'</span>);
    <span class="cm-variable">$scope</span>.<span class="cm-property">$digest</span>();
    <span class="cm-variable">expect</span>(<span class="cm-variable">input</span>.<span class="cm-property">$invalid</span>).<span class="cm-property">toBe</span>(<span class="cm-atom">true</span>);
});
 

 

Equality of shallow javascript arrays

I was working on a UI bit where you click on a modal to add or remove some items from a list. I wanted the modal to have a shallow copy of the items list, so that a cancel wouldn't update the original items list; only save would. Since a line of code is worth a thousand explanations: controller.js

<span class="cm-keyword">var</span> <span class="cm-variable">items</span> <span class="cm-operator">=</span> [<span class="cm-string">'Clean room'</span>, <span class="cm-string">'Do laundry'</span>];

<span class="cm-variable">openModal</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>() {
  <span class="cm-variable">modal</span>.<span class="cm-property">open</span>(<span class="cm-variable">items</span>.<span class="cm-property">slice</span>()); <span class="cm-comment">// passing in items to modal</span>
};

modal.js

<span class="cm-variable">remove</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">item</span>) {
  <span class="cm-variable">_</span>.<span class="cm-property">pull</span>(<span class="cm-variable">items</span>, <span class="cm-variable-2">item</span>);
}

<span class="cm-variable">cancel</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>() {
   <span class="cm-keyword">this</span>.<span class="cm-property">close</span>();
}
 

So when modal.cancel is called, controller.items should be unmodified. In my unit test (jasmine, below), I wanted to test that modal.items was a shallow copy. test.js

<span class="cm-variable">expect</span>(<span class="cm-variable">modal</span>.<span class="cm-property">items</span>).<span class="cm-property">not</span>.<span class="cm-property">toEqual</span>(<span class="cm-variable">controller</span>.<span class="cm-property">items</span>);
<span class="cm-comment">// Fail</span>

But that doesn't work. slice returns a new array, but maintains the references inside the array. This subtlety is lost on toEqual, and lodash/underscore's _.isEqual. Plain javascript to the rescue:

test.js

<span class="cm-variable">expect</span>(<span class="cm-variable">modal</span>.<span class="cm-property">items</span> <span class="cm-operator">!==</span> <span class="cm-variable">controller</span>.<span class="cm-property">items</span>).<span class="cm-property">toBe</span>(<span class="cm-atom">true</span>);
<span class="cm-comment">// Pass</span>

Oh yeah.

To cover my bases, I still used toEqual to test that controller.items was passed to modal.items so my test looked like:

<span class="cm-variable">expect</span>(<span class="cm-variable">modal</span>.<span class="cm-property">items</span>).<span class="cm-property">toEqual</span>(<span class="cm-variable">controller</span>.<span class="cm-property">items</span>);
<span class="cm-variable">expect</span>(<span class="cm-variable">modal</span>.<span class="cm-property">items</span> <span class="cm-operator">!==</span> <span class="cm-variable">controller</span>.<span class="cm-property">items</span>).<span class="cm-property">toBe</span>(<span class="cm-atom">true</span>);
<span class="cm-comment">// Pass</span>
 

 

 

 

Testing Angular directive's bindToController

In an Angular directive, you can use the bindToController property to set your $scope injections on the controller instance. It all works great, but things get murky when it comes to testing. If you want to pass in mock data in a directive controller during a test, you have to modify how you instantiate the controller. It turns out a third argument to $controller is boolean called later that will return a function. On that function's instance property, you can add your mock injections. Then when you invoke the function, you'll get the controller instance with your mocks attached. A quick example:

controller

 

<span class="cm-variable">angular</span>.<span class="cm-property">module</span>(<span class="cm-string">'todoapp'</span>, [])
.<span class="cm-property">directive</span>(<span class="cm-string">'todoList'</span>, <span class="cm-keyword">function</span>(){
  <span class="cm-variable">scope</span>: {
    <span class="cm-variable">items</span>: <span class="cm-string">'='</span>
  },
  <span class="cm-variable">bindToController</span>: <span class="cm-atom">true</span>
  <span class="cm-variable">controller</span>: <span class="cm-string">'Todo'</span>
})
.<span class="cm-property">controller</span>(<span class="cm-string">'Todo'</span>, <span class="cm-keyword">function</span>(){
  <span class="cm-keyword">var</span> <span class="cm-def">controller</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>;

  <span class="cm-variable-2">controller</span>.<span class="cm-property">amountOfTodos</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(){
    <span class="cm-keyword">return</span> <span class="cm-variable-2">controller</span>.<span class="cm-property">items</span>.<span class="cm-property">length</span>;
  };
});

test

<span class="cm-variable">describe</span>(<span class="cm-string">'getAmountOfTodos'</span>, <span class="cm-keyword">function</span>(){
    <span class="cm-keyword">var</span> <span class="cm-def">controller</span> <span class="cm-operator">=</span> <span class="cm-variable">$controller</span>(<span class="cm-string">'Todo'</span>, {}, <span class="cm-atom">true</span>);
    <span class="cm-variable">angular</span>.<span class="cm-property">extend</span>(<span class="cm-variable-2">controller</span>.<span class="cm-property">instance</span>, {
      <span class="cm-property">items</span>: [
          <span class="cm-string">'Take out laundry'</span>
      ]
    });
    <span class="cm-variable-2">controller</span> <span class="cm-operator">=</span> <span class="cm-variable-2">controller</span>();

    <span class="cm-variable">it</span>(<span class="cm-string">'returns the count of todo items'</span>, <span class="cm-keyword">function</span>(){
      <span class="cm-variable">expect</span>(<span class="cm-variable-2">controller</span>.<span class="cm-property">getAmountOfTodos</span>()).<span class="cm-property">toEqual</span>(<span class="cm-number">1</span>);
    });
});
<a href="http://stackoverflow.com/questions/25837774/bindtocontroller-in-unit-tests" target="_blank">Props.</a> and <a href="https://github.com/angular/angular.js/issues/9425" target="_blank">open issue</a>.

 

Making the most of ng-repeat and filters

Not too long ago, I wrote about storing the value of a filtered ng-repeat. But what if I wanted the display to show the filtered results, and I wanted to use the un-filtered results somewhere else in the template?

Consider this example:

<span class="cm-operator">&lt;</span><span class="cm-variable">input</span> <span class="cm-variable">ng</span><span class="cm-operator">-</span><span class="cm-variable">model</span><span class="cm-operator">=</span><span class="cm-string">"searchText"</span> <span class="cm-operator">/&gt;</span>
<span class="cm-operator">&lt;</span><span class="cm-variable">ul</span><span class="cm-operator">&gt;</span>
  <span class="cm-operator">&lt;</span><span class="cm-variable">li</span> <span class="cm-variable">ng</span><span class="cm-operator">-</span><span class="cm-variable">repeat</span><span class="cm-operator">=</span><span class="cm-string">"item in getItems() | filter:searchText as items"</span><span class="cm-operator">&gt;&lt;</span><span class="cm-string-2">/li&gt;</span>
<span class="cm-operator">&lt;</span><span class="cm-string-2">/ul&gt;</span>
<span class="cm-operator">&lt;</span><span class="cm-variable">span</span><span class="cm-operator">&gt;</span> <span class="cm-variable">items</span><span class="cm-operator">!&lt;</span><span class="cm-string-2">/span&gt;</span>

If getItems() returns two items, the span will show 2 items!. Once we start entering search text,getItems() will still return two items, but the repeat will be filtered. Then, the span would show 1 items! or 0 items! depending on what our searchText was.

To make sure the item count persists even when a user is filtering, you can modify how you assign the output of getItems():

<span class="cm-operator">&lt;</span><span class="cm-variable">li</span> <span class="cm-variable">ng</span><span class="cm-operator">-</span><span class="cm-variable">repeat</span><span class="cm-operator">=</span><span class="cm-string">"item in (items = getItems()) | filter:searchText"</span><span class="cm-operator">&gt;&lt;</span><span class="cm-string-2">/li&gt;</span>

In that example, items is being assigned before filtering. So our items will remain the same, even when a user is searching. And when they search, the display will only show the filtered results.

Putting it all together:

<span class="cm-operator">&lt;</span><span class="cm-variable">input</span> <span class="cm-variable">ng</span><span class="cm-operator">-</span><span class="cm-variable">model</span><span class="cm-operator">=</span><span class="cm-string">"searchText"</span> <span class="cm-operator">/&gt;</span>
<span class="cm-operator">&lt;</span><span class="cm-variable">ul</span><span class="cm-operator">&gt;</span>
  <span class="cm-operator">&lt;</span><span class="cm-variable">li</span> <span class="cm-variable">ng</span><span class="cm-operator">-</span><span class="cm-variable">repeat</span><span class="cm-operator">=</span><span class="cm-string">"item in (items = getItems()) | filter:searchText"</span><span class="cm-operator">&gt;&lt;</span><span class="cm-string-2">/li&gt;</span>
<span class="cm-operator">&lt;</span><span class="cm-string-2">/ul&gt;</span>
<span class="cm-operator">&lt;</span><span class="cm-variable">span</span><span class="cm-operator">&gt;</span> <span class="cm-variable">items</span><span class="cm-operator">!&lt;</span><span class="cm-string-2">/span&gt;</span>

These may be small things. But this week, they represented huge victories.

 

About the author

Ian McNally is a Front End Specialist and Senior Software Consultant for Stride. He writes at ia-n.com and tweets @_ianmcnally.