4: Adding a basic list using $resource for a restful query from rails

In this post we start building out our AngularJS client, using ngResource to pull data from our rails 4 server.  At the end of section 4 we should have a basic clubs list function operational.

In this post, I’m going to talk a bit more about what angular is actually doing, and how it connects to rails.  Hopefully it will be semi-understandable, but I’ll note that it’s taken me a few weeks to bend my brain around what angular is doing – it’s quite different than rails.

If you’ve dropped into the middle of this tutorial, you can get the code for part 3 of the tutorial from gitHub:PaulL.  If you’re looking for the other parts of this tutorial you can find them by going to the index page or hitting the tutorials menu above.

At the end of this section you should have something like:

Tutorial_3 clubs from server

The boilerplate app that we created has a bunch of functionality in it, but most of that functionality we don’t really want.  So step one is going to be to go through it all and firstly to rename everything to our new app name (league), and secondly to clean out the bits that we aren’t really using.

Let’s start off in the package.json file in the root directory.  This content looks like it’s getting packaged up and made available somewhere (not clear yet where).  Anyway, change the information at the top of this file to reflect our new app.  It’s reasonably important at this stage not to delete any of the items in here, as that will break grunt compile (and break it without any sensible error as to what has gone wrong):

{
  "author": "PaulL",
  "name": "league-tutorial-rails4",
  "version": "0.0.1",
  "homepage": "https://technpol.wordpress.com",
  "licenses": {
    "type": "MIT",
    "url": "https://github.com/PaulL1/league-tutorial-rails4/blob/master/LICENSE"
  },
  "bugs": "https://github.com/PaulL1/league-tutorial-rails4/issues",
  "repository": {
    "type": "git",
    "url": "git@github.com:PaulL1/league-tutorial-rails4.git"
  },
...

Next, we move on to the src directory. This directory is where all the source code lives, the code from here is built into the build directory by the grunt build command (and, for production usage, compiled into the angular_bin directory by the grunt compile command).

Let’s start by editing src/index.html. This is a standard web page, but has some extra angular directives in it. The most important is the ng-app, which tells the angular scripts what your app name is. Let’s change it:

<html ng-app="league" ng-controller="AppCtrl">

We delete all the twitter tags (no use for them at the moment), and put some information in that area:

<!-- social media tags -->
<meta property="og:title" content="league" />
<meta property="og:type" content="website" />
<meta property="og:url" content="localhost" />
<meta property="og:description" content="A sample angular/rails app that provides a sports league">

Change the title in the h3 block:

<h3 class="muted">League <small><a>

Other than that, we’ll leave the body alone for now, later we’re going to repurpose some of Josh’s layout to our purposes, for now we want to get the renaming out of the way and prove the app still works.

We tidy up the footer, we’ll still reference Josh’s good work, but make it a bit more specific to us:

<footer>
  <div>
    <div>
      <p>
        Based on:
        <a href="http://github.com/joshdmiller/ng-boilerplate">ngBoilerplate by Josh Miller</a>,
        <a href="http://www.angularjs.org">AngularJS</a>,
        <a href="http://getbootstrap.com">Bootstrap</a>,
        <a href="http://angular-ui.github.com/bootstrap">UI Bootstrap</a>,
        and
        <a href="http://fortawesome.github.com/Font-Awesome">Font Awesome</a>.
      </p>
    </div>
  </div>
</footer>

That leaves us with a web page that declares it’s an angular app called “league”, with the main client-side controller called “AppCtrl”. It has sub-urls of /home and /about, which angular will handle for us through calling the uiRoute function, which we’ll come to later.

Next, we move into src/app/app.js. In here we simply want to rename everything to league, we end up with a javascript model called ‘league’, this module has a couple of dependencies called league.home and league.about:

angular.module( 'league', [
  'templates-app',
  'templates-common',
  'league.home',
  'league.about',
  'ui.state',
  'ui.route'
])

.config( function myAppConfig ( $stateProvider, $urlRouterProvider ) {
  $urlRouterProvider.otherwise( '/home' );
})

.run( function run () {
})

.controller( 'AppCtrl', function AppCtrl ( $scope, $location ) {
})
;

We then need to edit src/app/home/home.js

angular.module( 'league.home', [

And similarly in src/app/about/about.js

angular.module( 'league.about', [

We edit the test scripts, starting with src/app/app.spec.js

    beforeEach( module( 'league' ) );

And src/app/home/home.spec.js

  beforeEach( module( 'league.home' ) );

We now run a build, by running

grunt build

This should push everything into build/, and therefore also into public/UI. So try going to the index page again on your rails app. It should show you almost exactly what we had before, except behind the scenes our app is now called “league”, and the footer has changed slightly.

If you check your browser’s javascript console you may have an error regarding the Roboto font, depending on whether or not this pull request has merged on ng-boilerplate..  We can fix this by moving it into the index page, just below the font awesome load:

    <!-- font awesome from BootstrapCDN -->
    <link href="http://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css" rel="stylesheet"> 

+    <!-- Roboto font from google -->
+    <link href='http://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>

We then also need to modify src/less/main.less to remove the reference to the local install, so delete the following lines:

-@font-face {
-   font-family: 'Roboto';
-   font-style: normal;
-   font-weight: 400;
-   src: local('Roboto Regular'), local('Roboto-Regular'), url(fonts/Roboto-Regular.woff) format('woff');
-}
-

Run grunt build again, and hopefully the Roboto error will have gone away.

It’s still not connecting to our rails back end yet, but we’re sneaking up on it. Let’s create a clubs entity. Create a directory src/app/club. Create a file within it src/app/club/clubs.tpl.html. This is the page that the uiRoute will take us to when someone goes to the /club route. For now, let’s make this pretty simple:

<div>
  <h1>Club functions</h1>
  <p>Soon this will show a list of all the clubs, based on information from the server</p>
</div>

<div>
  <table>
    <thead>
      <td>Name</td>
      <td>Contact Officer</td>
    </thead>
    <tr>
      <td>sample club</td>
      <td>sample officer</td>
    </tr>
  </table>
</div>

(For those of you who also completed the rails 3 tutorial, note that we’re using the plural form for our lists now, so this is clubs, not club.)

Next, we create src/app/club/club.js, which is heavily based on home.js. What this provides is:

  • a module, with a set of dependencies that are needed for the page to work properly. The only dependencies at present is ui.state. There was a plusOne service, but this was doing some work with google plus, we don’t need it.
  • a route, which tells angular that when someone goes to the url /club, that we want to use the html template club.tpl.html, and that template has a controller ClubCtrl
  • the controller itself, which is a simple function that currently only sets the tab title

Note that we’re using the stateProvider routing functionality, otherwise known as ui-router, which is an alternate router to the default angular router. This router is used to associate URLs with templates and controllers, and here we’re only scratching the surface of what it can do, there’s a lot of good starter documentation for it, with more detail around how to use it for stateful routing. Go ahead and create src/app/club/club.js:

/**
 * Club module
 */
angular.module( 'league.club', [
  'ui.state'
])

/**
 * Define the route that this module relates to, and the page template and controller that is tied to that route
 */
.config(function config( $stateProvider ) {
  $stateProvider.state( 'clubs', {
    url: '/clubs',
    views: {
      "main": {
        controller: 'ClubsCtrl',
        templateUrl: 'club/clubs.tpl.html'
      }
    },
    data:{ pageTitle: 'Clubs' }
  });
})

/**
 * And of course we define a controller for our route.
 */
.controller( 'ClubsCtrl', function ClubsController( $scope ) {
});

(For those of you who did the rails 3 tutorial, note that this is now the ClubsController, not the ClubController, and all the urls and states change accordingly).

Open up src/index.html again, and change the last link (issues) to take us to clubs instead.

  <li ui-route="/clubs" ng-class="{active:$uiRoute}">
    <a href="#/clubs">
      <i></i>
      Clubs
    </a>
  </li>

And change src/app/app.js to refer to the new module:

  'templates-app',
  'templates-common',
  'league.home',
  'league.about',
  'league.club',
  'ui.state',
  'ui.route'
])

Run grunt build again and see what we have. You should get a page with a clubs link on it, and when you click that link, it should take you to a clubs page. If it doesn’t, check the developer tools on your browser, look at the javascript console, and look at the javascript source to see whether you have the right source showing up in your browser (if you don’t, perhaps your grunt build didn’t work).

We’d like to create automated tests as we go.  So far all we’ve created is a state provider, which is effectively navigation.  Navigation is tested in end to end tests.  This is “coming soon” to ng-boilerplate according to the ng-boilerplate issues list.  We also see on the angularJS end to end testing page that protractor is about to become the standard.  We’re going to skip this until the next section of the tutorial, which will specifically focus on setting up end-2-end testing using protractor.

Next, we want to put some data into that page based on a client-side model. Models in angular are just variables that are created within the controller. So let’s create a clubs variable within the controller in src/app/club/club.js. Angular makes all variables attributes on the $scope variable – this is basically trickiness to provide variable scoping in a language that doesn’t really support scoping. There’s also magic running that makes all $scope variables that are declared in the controller available to the associated view:

.controller( 'ClubsCtrl', function ClubsController( $scope ) {
  $scope.clubs = [
    {name: "hard coded club 1", contact_officer: "hard coded officer 1"},
    {name: "hard coded club 2", contact_officer: "hard coded officer 2"}
  ];
});

We’ll then use data binding to put this information into the view. What we want to do is to iterate through all the clubs, creating a row in the table for each club within the clubs list. The syntax for this is "ng-repeat="club in clubs". The route setup we did earlier means that this view has access to the data within the ClubCtrl controller, so it can get to the variable called $scope.clubs. Angular will iterate through the array of clubs, duplicating the html block that we attached the ng-repeat to, and filling in data. To fill in data we can use the {{club.variable}} notation. Make the following changes to club.tpl.html:

  <table>
    <thead>
      <td>Name</td>
      <td>Contact Officer</td>
    </thead>
    <tr ng-repeat="club in clubs">
      <td>{{club.name}}</td>
      <td>{{club.contact_officer}}</td>
    </tr>
  </table>

Run grunt build, and go back to your page to see what you have.  You should get a list of two clubs in your page, which looks something like the following:

Tutorial_3 hard coded clubs

This is now the point where we can add tests.  We’re going to add a test of our controller to verify that it’s correctly creating 2 items in the list (I know, that’s not much of a test, but it’s the logic we have now, and we’re staying away from e2e tests).

Create the file src/app/club/club.spec.js, which will be the unit test for our club logic.  Into this file add:

/**
 * Unit tests for the club functionality
 */
describe( 'Clubs list controller', function() {
  //mock Application to allow us to inject our own dependencies
  beforeEach(angular.mock.module('league'));

  //mock the controller for the same reason and include $rootScope and $controller
  beforeEach(angular.mock.inject(function($rootScope, $controller){
    //create an empty scope
    scope = $rootScope.$new();

    //declare the controller and inject our empty scope
    $controller('ClubsCtrl', {$scope: scope});
  }));

  // tests start here
  it('Has two clubs defined', function(){
    expect(scope.clubs.length).toEqual(2);
  });  
});

This is following a template from tuesdaydeveloper, and is doing a few things:

  • Declaring that we’re testing the club controller
  • Before each test, mocking out the league module (todo: why is this a mock and not an include?)
  • Before each test, creating a new scope and injecting it into the test.  This is creating a scope variable in the testing code and pushing that scope variable into the controller, the effect of this is that the unit testing code gets access to all the variables within the controller and can verify their values in the local scope variable
  • Defines the controller that we’re testing, and calls new on it
  • Note that tuesdaydeveloper uses toBe to test the result, the clear recommendation is to use toEqual unless you really mean “exact same object”.  For scalar variables like integers both will work, but toEqual is better practice

Run grunt build to see what happens, and try changing the toEqual from 2 to 3 and back again.  If you’re using grunt watch, rather than grunt build, note that watch doesn’t notice new files, so you’ll have to stop and restart grunt when you create a new file.

As our final step in this post, we’re going to tie the data to the rails server instead of hard coded information.  To do that we need to install ng-resource, which provides the functionality to connect to our rails server. We use bower to install it, and we save that into the bower.json file.

bower install angular-resource --save-dev

This should give you a choice of angular versions, ideally pick 1.2.1 or higher, and prefix with ! to persist it to your bower.json file (this will probably also upgrade your angularjs version).  There is new functionality in angular-resource 1.2 that we want, if you didn’t get at least 1.2 then run an upgrade:

bower update

This should give you options for upgrading your functionality. If it doesn’t, you may need to edit bower.json, and change the version number for both angular and angular-resource to ~1.2 then run bower update to force this upgrade. If it gives you an option to do so, prefix your selection with ! to persist it to bower.json.

Edit build.config.js to make angular-resource available (note the addition of a comma on the line above):

      'vendor/angular-ui-utils/modules/route/route.js',
      'vendor/angular-resource/angular-resource.js'

The magic in the build scripts will automatically include all these into the html, and when you compile your application for web deployment (into the bin directory) the scripts will automatically include only the single concatenated javascript file.

Make two edits in the src/app/club/club.js file, one creates a resource, which connects to the rails backend.  The second tells the controller to call this resource to get the clubs, rather than hard coding them.  Don’t miss that we added “ClubRes” to the function definition for ClubController itself.

.controller( 'ClubsCtrl', function ClubsController( $scope, ClubRes ) {
  $scope.clubs = ClubRes.query();
})

/**
 * Add a resource to allow us to get at the server
 */
.factory( 'ClubRes', function ( $resource )  {
  return $resource('../clubs.json');
})
;

We also need to modify the dependencies for this module – it now has a dependency on ngResource:

angular.module( 'league.club', [
  'ui.state',
  'ngResource'
])

Run grunt build again (it will give errors on the unit tests – ignore those for now), and then look on the UI to see whether you are now getting data from the server. Again, the developer tools on your browser can help diagnose any problems with the UI. I did occasionally get problems where my sqlite database had been clobbered, perhaps when we merged ng-boilerplate in.  If that happens, try running rake db:migrate again, and restart your rails server.

You should have a page like the below, which is returning data from your rails server:

Tutorial_3 clubs from server

Now we have some more interesting logic to write unit tests on.  What we’re going to do is to write a mock, which replaces the ngResource call with a pre-canned result.  In this way we can test the Angular logic on it’s own without relying on the server.  Let’s start off by creating that mock, again we’re relying on the template from tuesdaydeveloper, with a bit of help from arsuceno in the angular documentation comments:

/**
 * Unit tests for the club functionality
 */
describe( 'Clubs list controller', function() {
  var scope, httpBackend;

  //mock Application to allow us to inject our own dependencies
  beforeEach(angular.mock.module('league'));

  //mock the controller for the same reason and include $rootScope and $controller
  beforeEach(angular.mock.inject(function($rootScope, $controller, _$httpBackend_ ){
    //create an empty scope
    scope = $rootScope.$new();

    scope.httpBackend = _$httpBackend_;
    scope.httpBackend.expect('GET', '../clubs.json').respond([
      {"contact_officer":"Officer 1","created_at":"2012-02-02T00:00:00Z","date_created":"2012-01-01T00:00:00Z","id":1,"name":"Club 1","updated_at":"2012-03-03T00:00:00Z"},
      {"contact_officer":"Officer 2","created_at":"2012-02-02T00:00:00Z","date_created":"2012-01-01T00:00:00Z","id":2,"name":"Club 2","updated_at":"2012-03-03T00:00:00Z"}]);

    //declare the controller and inject our empty scope
    $controller('ClubsCtrl', {$scope: scope});
  }));

  // tests start here
  it('Has two clubs defined', function(){
    scope.$digest();
    scope.httpBackend.flush();
    expect(scope.clubs.length).toEqual(2);
  });

  it('First club\'s contact officer is as expected', function(){
    scope.$digest();
    scope.httpBackend.flush();
    expect(scope.clubs[0].contact_officer).toEqual('Officer 1');
  });

});

This code introduces a mock backend for http, which at the moment has a single repeatable response that is given whenever it’s asked for clubs.json.  We define that mock up front, and inject the mock $httpBackend into the controller.  We also configure our httpBackend mock to expect a call to clubs.json, and tell it what we want it to return when that call comes.

On entering the controller we call $digest, which triggers the angular asynch processing.  You could alternatively wrap your call to the controller in a $apply, I found that less intuitive.  We then call flush on httpBackend, which causes it to call our mock (the angularJS docs describe this process, but they ignore the $digest bit).  The flush tells httpBackend to check the expected calls, and return the values asked for.  We  should get our results as expected.  In a later tutorial we refactor some of this into a beforeEach block to make it more reusable, but this is working and is clear for now.

When you run grunt build you should now have 4 unit tests that are all passing.

The final code position for this tutorial is on github for tutorial_4.

In the next tutorial we’ll look at end to end testing.

Advertisements

5 thoughts on “4: Adding a basic list using $resource for a restful query from rails

  1. Pingback: AngularJS and Rails 4 CRUD application using ng-boilerplate and twitter bootstrap 3: Tutorial Index | technpol

  2. Pingback: 3: Installing ng-boilerplate, and serving it through rails | technpol

  3. Thank you for great tutorial!
    I’m newbie in angularjs and came from official guide from google and there’s recommends use everywere inline dependency injection with anonym function as best practice. But in thit tutor i’v seen that ngBoilerplate used named functions without dependesy injection.

    Could you clarify this point?

    Thanks in advance.

  4. Rudolf, glad you found it useful.

    I have to confess the dependency injection generally has me confused. For a while I had it both ways in different places, mostly because I didn’t know any better. What I have now works, and processes satisfactorily through minification. On that basis I’ve kept using it. 🙂 I think like many things in javascript there are multiple ways to do it, and none are necessarily better. So whatever makes you happy is probably what you should use.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s