12 Feb 2014, 09:30

Sailing app - part 5 - Removing some dust and improving the databinding with bindOnce

It's more than one month we last talked about our sailing app. Let's take some time to refresh it.

Removing the dust

Refreshing our tooling chain

When we initiated our environment, we used npm to install yeoman, bower and grunt mainly.

Let's update first the global package we installed (ie the one we install with npm install -g <package>) :

npm update -g

npm will check against its registry if updates are available. If yes, it will install the updates.

Refreshing the library we use for our project

If you go to your root directory and then run npm update and depending on how strict you defined your version policy in your package.json file, you may or may not get updates.

Let's take some time to review this : npm applies the semver principle to define how it should behave when updating.

In the package.json file, we have the simplified version below :

{
  "name": "vmcollection",
  "version": "0.0.0",
  "dependencies": {},
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-autoprefixer": "~0.4.0",
    "grunt-concurrent": "~0.4.1",
    "grunt-contrib-clean": "~0.5.0",
    [...]
  },
  "engines": {
    "node": ">=0.8.0"
  },
}

Regarding grunt, the "tilde" would mean that when updating, it should take a version reasonably close to 0.4.1. So it would be any version number as of 0.4.x but not 0.5.0 or higher. It could have been written as "0.4.x" or "~0.4". As you can notice, all the dependencies in package.json will use the tilde notation to allow you smooth upgrades. Indeed, if developers use the right semver syntax, you should only get patch releases.

Regarding node, It would require any version greater than 0.8.

So the idea here is that you can manage your dependencies as you wish. From a strict point of view like "0.4.0" and whatever happens you will stick to it. Or have smooth upgrades (with tilde notation) or have more "risky" upgrade strategy with up to the greater or equal than version X.

You can test it still works by running the command we saw before like grunt test, grunt server, grunt build.

No regression ? Perfect, we can go further.

Refreshing our frontend libs

Now our tooling chain is updated, there may be some new version of AngularJS or JQuery that were released in the meantime. As for the package.json file above, bower have its own bower.json file and using the same principles.

So just run bower update and you're done. You will get the latest 1.2.x version of AngularJS and the related dependencies.

Everything is still fine in your tests and so on ? So let's improve our code.

Improving our binding with bindOnce

If you have some immutable data you manipuate, bindOnce may interest you as it will reduce the number of $watch (ie models mutation/changes in your scope - remember that your scope make the link between your model and your view.) So in a loop, you will have a $watch item for each ng-* directive you have. In our case, with 1800+ lines and 5 items per line, we have 9000+ $watch initiated. Maybe we don't need that much as we do not change the value of the data. That's the purpose of bindOnce.

So let's install it :

bower install angular-bindonce --save

In app/index.html, load the bindonce.js file :

    <!-- build:js scripts/modules.js -->
    <script src="https://nicolas.steinmetz.fr/post/2014/02/12/bower_components/angular-resource/angular-resource.js"></script>
    <script src="https://nicolas.steinmetz.fr/post/2014/02/12/bower_components/angular-cookies/angular-cookies.js"></script>
    <script src="https://nicolas.steinmetz.fr/post/2014/02/12/bower_components/angular-sanitize/angular-sanitize.js"></script>
    <script src="https://nicolas.steinmetz.fr/post/2014/02/12/bower_components/angular-route/angular-route.js"></script>
    <script src="https://nicolas.steinmetz.fr/post/2014/02/12/bower_components/angular-bindonce/bindonce.js"></script>
    <!-- endbuild -->

In app/scripts/app.js, add the bindOnce as a module in your app :

angular.module('vmCollectionApp', [
  'ngCookies',
  'ngResource',
  'ngSanitize',
  'ngRoute',
  'pasvaz.bindonce'
])
[...]

Before updating our template, let's introduce ngBind ; up to now, we had in app/views/index.html:

  <tr class="boat" ng-repeat="item in issuelist | filter:basicboatfilter | orderBy:orderProp">
    <td>{{ item.model }}</td>
    <td>{{ item.boat_type }}</td>
    <td>{{ item.category }}</td>
    <td>{{ item.month }}</td>
    <td>{{ item.year }}</td>
  </tr>

As you can read, it's recommended to use the ngBind syntax over the one above to avoid some template compilation issues. So we now have :

  <tr class="boat" ng-repeat="item in issuelist | filter:basicboatfilter | orderBy:orderProp">
    <td ng-bind="item.model"></td>
    <td ng-bind="item.boat_type"></td>
    <td ng-bind="item.category"></td>
    <td ng-bind="item.month"></td>
    <td ng-bind="item.year"></td>
  </tr>

And so now you can more easily see where our 9000+ $watch are linked with.

So let's improve it with the bindOnce directives (bo-*):

  <tr class="boat" bindonce ng-repeat="item in issuelist | filter:basicboatfilter | orderBy:orderProp">
    <td bo-text="item.model"></td>
    <td bo-text="item.boat_type"></td>
    <td bo-text="item.category"></td>
    <td bo-text="item.month"></td>
    <td bo-text="item.year"></td>
  </tr>

So now, if all works well, we only have one single $watch at the ng-repeat level and no longer within the loop. Unfortunately, I don't know yet how to show it easily.

Last but not the least, at test level, don't forget to add in karma.conf.js, in the files section the reference to bindonce.js :

    // list of files / patterns to load in the browser
    files: [
      'app/bower_components/angular/angular.js',
      'app/bower_components/angular-mocks/angular-mocks.js',
      'app/bower_components/angular-resource/angular-resource.js',
      'app/bower_components/angular-cookies/angular-cookies.js',
      'app/bower_components/angular-sanitize/angular-sanitize.js',
      'app/bower_components/angular-route/angular-route.js',
      'app/bower_components/angular-bindonce/bindonce.js',
      'app/bower_components/jquery/jquery.js',
      'app/scripts/*.js',
      'app/scripts/**/*.js',
      'app/vendor/**/*.js',
      'test/mock/**/*.js',
      'test/spec/**/*.js'
    ],

There is nothing to do on build grunt task as the bower_components directory in which bindOnce is installed is already managed.

Final good news is that bindOnce will be integrated in AngularJS 1.3 !