DailyJS

DailyJS

The JavaScript blog.


Tagangularfeeds
Featured

mvc angularjs bower angularfeeds

AngularJS: Installation

Posted on .

I've been working on tidying up the AngularJS tutorial project, which you can find here: GitHub: alexyoung / djsreader. The project was originally created with Yeoman, as part of the topics I wanted to cover on the tutorial series. However, a fresh checkout of my code from GitHub wouldn't run out of the box, so I wrote up some instructions in the readme.

Although I'd prefer it if people could follow the tutorial series from part 1 and build their own version, it's not always possible as dependencies change over time. I get asked about this a lot, and it's just a consequence of the style of tutorials I write on this blog. It would be nice if I could maintain these articles and keep them working with the latest version of each of the major dependencies, but I have to earn a living!

If you just want to check out the AngularJS tutorial source and play with it I've included instructions below.

Installing the Dependencies

To run the project, you'll need to install the following things:

  • Node 0.10.x
  • Bower (npm install -g bower)
  • Grunt (npm install -g grunt-cli)
  • Compass (gem install compass)

Hopefully you already have Node -- which means Bower and Grunt should be easy to install. Compass is annoying: one of the Grunt tasks builds Bootstrap from Sass, which needs the command-line compass Ruby script. It's annoying because Ruby is a mess and unless you work with it professionally you really don't want to have to deal with Ruby version management. Fortunately, I found gem install compass on a fresh Mountain Lion machine worked out of the box. The Ruby version is 1.8.7, which is dated. I noticed sudo gem install compass resulted in a script that wouldn't work and I didn't bother to find out why. I may replace the Grunt task to get rid of this dependency so we can just depend on Node.

Once you've got that installed, npm install will fetch Grunt's dependencies. Then run bower install to get the client-side libraries.

Using and Running It

The gruntfile that Yeoman generated has a few goodies built in. You can build the project with grunt build, run tests with grunt test, and start a server with grunt server -- this is the one you probably want, and if things don't seem to work in the browser just check you've built it first.

Hacking It

If all you want to do is try out some AngularJS ideas or change the HTML, you should familiarise yourself with the project tree. Most of what you want is in app/, and in particular app/scripts/controllers/main.js is what most of the tutorials refer to and app/views/main.html is the corresponding template.

Now you should be able to run the project without following the individual tutorials. Hopefully newcomers can at least try the project out relatively painlessly.

Featured

mvc angularjs bower angularfeeds

AngularJS: Iterators and Filters

Posted on .

AngularJS has a rich expression-based system for filtering and ordering data based on predicates. The orderBy filter can be used with the ng-repeat directive:

<ul>  
  <li ng-repeat="item in stories | orderBy:predicate:date"><a href="{{ item.link.href }}">{{ item.title }}</a></li>
</ul>  

Today we're going to use orderBy inside a controller using dependency injection to organise multiple feeds into a river of news sorted by date.

Iterating in Controllers

Before sorting and displaying stories, we need to collect them into a suitable data structure. An array will suffice (app/scripts/controllers/main.js):

$scope.stories = [];

Next we need to append stories to this collection, but only if they haven't already been added. Let's use a function to encapsulate that away from fetching stories:

$scope.fetchFeed = function(feed) {
  feed.items = [];

  var apiUrl = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%3D'";
  apiUrl += encodeURIComponent(feed.url);
  apiUrl += "'%20and%20itemPath%3D'feed.entry'&format=json&diagnostics=true&callback=JSON_CALLBACK";

  $http.jsonp(apiUrl).
    success(function(data) {
      if (data.query.results) {
        feed.items = data.query.results.entry;
      }
      addStories(feed.items);

The addStories function just needs to loop over each feed item to determine if it's already been added to $scope.stories. The angular.forEach API in module ng is the perfect way to do this:

function addStories(stories) {  
  var changed = false;
  angular.forEach(stories, function(story, key) {
    if (!storyInCollection(story)) {
      $scope.stories.push(story);
      changed = true;
    }
  });
}

As you can see, forEach accepts an array and a function to call for each item. The storyInCollection function now needs to loop over each existing story to see if it's already been added. Figuring out which story is unique is easy because feeds have an id value:

function storyInCollection(story) {  
  for (var i = 0; i < $scope.stories.length; i++) {
    if ($scope.stories[i].id === story.id) {
      return true;
    }
  }
  return false;
}

Storing Data

Now we've got a list of stories in our scope, we need to sort them by date just like a real feed reader. Whenever addStories changes the list of stories we should sort it. AngularJS doesn't really have any fancy functional methods like map or some, which you can find in ECMAScript 5 anyway, but it does provide API access to the filtering and sorting modules that are typically used in templates.

To access this functionality you'll need to load $filter:

angular.module('djsreaderApp')  
  .controller('MainCtrl', function($scope, $http, $timeout, $filter) {

$filter will return a function that knows how to sort or filter arrays. That means you need to call it with the name of the desired method, then call the value returned with an array and an expression: $filter(filter)(array, expression). To add sorting to our feeds, call $filter()() and update the $scope.stories array:

// At the end of addStories
if (changed) {  
  $scope.stories = $filter('orderBy')($scope.stories, 'date');
}

The only thing left to do is update the template in app/views/mail.html:

<ul>  
  <li ng-repeat="item in stories"><a href="{{ item.link.href }}">{{ item.title }}</a></li>
</ul>  

If you add multiple feeds using the app's web interface you should see them combined into a river of news.

Conclusion

The river of news view

You can find this code in commit ff4d6a6.

Featured

mvc angularjs bower angularfeeds

AngularJS: Form Validation

Posted on .

This week we're going to look at form validation with AngularJS. Angular has several directives that support form field validation, and they're based on the HTML5 form validators. You can specify that a field is required, a certain size, a certain type, and should match a given pattern.

URL Validation

Chrome's validation message

This tutorial series is about a feed reader, so it's lucky that one of the standard HTML5 validators is for checking URLs. It can be used by adding the type="url" attribute to an input. Angular supports this through the input url directive. It takes various options, of which we're interested in required and ng-model.

The ng-model directive allows the input to be linked to a model, but any Angular expression can be used. The form directive allows forms to be managed with Angular, and bound to controllers.

Just by adding a form and an input with type="url" will result in some basic validation support (in app/views/main.html):

<form name="newFeed">  
  URL: <input size="80" name="url" ng-model="newFeed.url" type="url" required>
  <button ng-click="addFeed(newFeed)">Add Feed</button>
</form>  

However, this won't quite work with the controller code that I wrote in the previous parts because addFeed isn't set up to check validation.

Checking Validation State

In a controller, a bound value can be interrogated for the validation status by checking the $valid property. The previous addFeed, in app/scripts/controllers/main.js, can be changed as follows:

$scope.addFeed = function(feed) {
  if (feed.$valid) {
    // Copy this feed instance and reset the URL in the form
    $scope.feeds.push(feed);
    $scope.newFeed.url = {};
  }
};

This should work, but it does one thing wrong: $scope.newFeed.url can't be reset by assigning it to an object literal, because newFeed is now decorated with internal properties to support validation. Instead, copy the new object, and reset the values in newFeed:

$scope.addFeed = function(feed) {
  if (feed.$valid) {
    // Copy this feed instance and reset the URL in the form
    var newFeed = angular.copy(feed);
    $scope.feeds.push(newFeed);
    $scope.fetchFeed(newFeed);
    $scope.newFeed.url = '';
  }
};

Fighting with HTML5

We should probably add error messages that are cross-browser compatible. To do that, you can use the ng-show directive:

<form name="newFeed" novalidate>  
  URL: <input size="80" name="url" ng-model="newFeed.url" type="url" required>
  <button ng-click="addFeed(newFeed)">Add Feed</button>
  <span class="error" ng-show="newFeed.$error.required">Required!</span>
  <span class="error" ng-show="newFeed.$error.url">Invalid URL format!</span>
</form>  

The ngShow directive can conditionally show part of the DOM based on an Angular expression -- in this case the validation results are checked. Incidentally, validation results can be found in the $error property fo the model.

Also notice that I added the novalidate attribute to the form; if you don't do this HTML5 validations will still kick in, which causes confusing behaviour.

Disabling the Button

Another nice touch is to use ng-disabled to disable the button when an invalid URL has been entered. The ngDisabled directive takes an Angular expression, like the previous directives discussed here:

<form name="newFeed" novalidate>  
  URL: <input size="80" name="url" ng-model="newFeed.url" type="url" required>
  <button ng-disabled="!newFeed.$valid" ng-click="addFeed(newFeed)">Add Feed</button>
  <span class="error" ng-show="newFeed.$error.required">Required!</span>
  <span class="error" ng-show="newFeed.$error.url">Invalid URL format!</span>
</form>  

The difference here is I've used ! to negate the expression: !newFeed.$valid. Yes, it's really that easy!

Conclusion

There's more to expressions than simple model-based truth tests -- you can do pretty much anything short of control flow statements. For more, see Angular Developer Guide, Expressions.

The latest commit for this project was 0dcc996.

Featured

mvc grunt angularjs bower angularfeeds

AngularJS: Adding Dependencies

Posted on .

Adding Dependencies with Bower

This tutorial is really about Yeoman, Bower, and Grunt, because I still feel like it's worth exploring the build system that I introduced for this AngularJS project. I appreciate that the number of files installed by Yeoman is a little bit bewildering, so we're going to take a step back from AngularJS and look at how dependencies work and how to add new dependencies to a project.

Although Yeoman helps get a new project off the ground, it takes a fair amount of digging to figure out how everything is laid out. For example: let's say we want to add sass-bootstrap to djsreader -- how exactly do we do this?

Yeoman uses Bower for managing dependencies, and Bower uses component.json (or bower.json by default in newer versions). To add sass-bootstrap to the project, open component.json and add "sass-bootstrap": "2.3.x" to the dependencies property:

{
  "name": "djsreader",
  "version": "0.0.0",
  "dependencies": {
    "angular": "~1.0.5",
    "json3": "~3.2.4",
    "es5-shim": "~2.0.8",
    "angular-resource": "~1.0.5",
    "angular-cookies": "~1.0.5",
    "angular-sanitize": "~1.0.5",
    "sass-bootstrap": "2.3.x"
  },
  "devDependencies": {
    "angular-mocks": "~1.0.5",
    "angular-scenario": "~1.0.5"
  }
}

Next run bower install to install the dependencies to app/components. If you look inside app/components you should see sass-bootstrap in there.

Now the package is installed, how do we actually use it with our project? The easiest way is to create a suitable Grunt task.

Grunt

Grunt runs the djsreader development server and compiles production builds that can be dropped onto a web server. Gruntfile.js is mostly configuration -- it has the various settings needed to drive Grunt tasks so they can build our project. One task is compass -- if you search the file for compass you should see a property that defines some options for compiling Sass files.

The convention for Grunt task configuration is taskName: { argument: options }. We want to add a new argument to the compass task for building the Bootstrap Sass files. We know the files are in app/components/sass-bootstrap, so we just need to tell it to compile the files in there.

Add a new property to compass called bootstrap. It should be on line 143:

compass: {  
  // options/dist/server
  bootstrap: {
    options: {
      sassDir: '<%= yeoman.app %>/components/sass-bootstrap/lib',
      cssDir: '.tmp/styles'
    }
  }
}

Near the bottom of the file add an entry for compass:bootstrap to grunt.registerTask('server', [ and grunt.registerTask('build', [:

grunt.registerTask('server', [  
  'clean:server',
  'coffee:dist',
  'compass:server',
  'compass:bootstrap', /* This one! */
  'livereload-start',
  'connect:livereload',
  'open',
  'watch'
]);

This causes the Bootstrap .scss files to be compiled whenever a server is started.

Now open app/index.html and add styles/bootstrap.css:

<link rel="stylesheet" href="styles/bootstrap.css">  
<link rel="stylesheet" href="styles/main.css">  

Conclusion

Angular/Bootstrap

The settings files Yeoman created for us makes managing dependencies easy -- there's a world of cool things you can find with bower search and try out.

This week's code is in commit 005d1be.

Featured

mvc angularjs angularfeeds

AngularJS: More on Dependency Injection

Posted on .

In the AngularJS tutorials I've been writing, you might have noticed the use of dependency injection. In this article I'm going to explain how dependency injection works, and how it relates to the small tutorial project we've created.

Dependency injection is a software design pattern. The motivation for using it in Angular is to make it easier to transparently load mocked objects in tests. The $http module is a great example of this: when writing tests you don't want to make real network calls, but defer the work to a fake object that responds with fixture data.

The earlier tutorials used dependency injection for this exact use case: in main controller, the MainCtrl module is set up to load the $http module which can then be transparently replaced during testing.

angular.module('djsreaderApp')  
  .controller('MainCtrl', function($scope, $http, $timeout) {

Now forget everything I just said about dependency injection, and look at the callback that has been passed to .controller in the previous example. The $http and $timeout modules have been added by me because I want to use the $http service and the $timeout service. These are built-in "services" (an Angular term), but they're not standard arguments. In fact, I could have specified these arguments in any order:

angular.module('djsreaderApp')  
  .controller('MainCtrl', function($scope, $timeout, $http) {

This is possible because Angular looks at the function argument names to load dependencies. Before you run away screaming about magic, it's important to realise that this is just one way to load dependencies in Angular projects. For example, this is equivalent:

angular.module('djsreaderApp')  
  .controller('MainCtrl', ['$scope', '$http', '$timeout'], function($scope, $http, $timeout) {

The array-based style is more like AMD, and requires a little bit of syntactical overhead. I call the first style "introspective dependency injection". The array-based syntax allows us to use different names for the dependencies, which can be useful sometimes.

This raises the question: how does introspective dependency injection cope with minimisers, where variables are renamed to shorter values? Well, it doesn't cope with it at all. In fact, minimisers need help to translate the first style to the second.

Yeoman and ngmin

One reason I built the tutorial series with Yeoman was because the Angular generator includes grunt-ngmin. This is a Grunt task that uses ngmin -- an Angular-aware "pre-minifier". It allows you to use the shorter, introspective dependency injection syntax, while still generating valid minimised production builds.

Therefore, building a production version of djsreader with grunt build will correctly generate a deployable version of the project.

Why is it that almost all of Angular's documentation and tutorials include the potentially dangerous introspective dependency injection syntax? I'm not sure, and I haven't looked into it. I'd be happier if the only valid solution was the array-based approach, which looks more like AMD which most of us are already comfortable with anyway.

Just to prove I'm not making things up, here is the minimised source for djsreader:

"use strict";angular.module("djsreaderApp",[]).config(["$routeProvider",function(e){e.when("/",{templateUrl:"views/main.html",controller:"MainCtrl"}).otherwise({redirectTo:"/"})}]),angular.module("djsreaderApp").controller("MainCtrl",["$scope","$http","$timeout",function(e,r,t){e.refreshInterval=60,e.feeds=[{url:"http://dailyjs.com/atom.xml"}],e.fetchFeed=function(n){n.items=[];var o="http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%3D'";o+=encodeURIComponent(n.url),o+="'%20and%20itemPath%3D'feed.entry'&format=json&diagnostics=true&callback=JSON_CALLBACK",r.jsonp(o).success(function(e){e.query.results&&(n.items=e.query.results.entry)}).error(function(e){console.error("Error fetching feed:",e)}),t(function(){e.fetchFeed(n)},1e3*e.refreshInterval)},e.addFeed=function(r){e.feeds.push(r),e.fetchFeed(r),e.newFeed={}},e.deleteFeed=function(r){e.feeds.splice(e.feeds.indexOf(r),1)},e.fetchFeed(e.feeds[0])}]);

The demangled version shows that we're using the array-based syntax, thanks to ngmin:

angular.module("djsreaderApp").controller("MainCtrl", ["$scope", "$http", "$timeout",  

Internals

In case you're wondering how the introspective dependency injection style works, then look no further than annotate(fn). This function uses Function.prototype.toString to extract the argument names from the JavaScript source code. The results are effectively cached, so even though this sounds horrible it doesn't perform as badly as it could.

Conclusion

Nothing I've said here is new -- while researching this post I found The Magic Behind Dependency Injection by Alex Rothenberg, which covers the same topic, and the Angular Dependency Injection documentation outlines the issues caused by the introspective approach and suggests that it should only be used for pretotyping.

However, I felt like it was worth writing an overview of the matter, because although Yeoman is great for a quick start to a project, you really need to understand what's going on behind the scenes!