Follow me if you want to grow your full-stack JavaScript skills with my screencasts and courses.

Angular with Yeoman

More about yeoman

yeoman is comprised of three tools:

Yo - which is used for scaffolding

Grunt - gives us a whole bunch of stuff like serving the app while we code it up, compiling stuff like coffee script and sass, and also doing intense build stuff like combining and minifying your files. If you've worked with ruby, you can think of it of 'sorta like rake'.

Bower: used to pull in our dependencies. Its for html apps what bundler is for ruby and npm is for nodejs.

Setting up the environment

First off I'm going to create a directory for a simple budget application I'm going to code up.

terminal
    
    $ mkdir budget-app && cd budget-app
    
  

I'm installing yo a bit different than usual, it's recommended that you install it globally using the -g flag. I opted to do it locally so that we don't have any clashes with different versions or stuff like that.

terminal
    
    $ npm install yo
    
  

So now we've got our scaffolding engine installed, but we need to let it know how to generate an angular app. We install the angular generator using npm. Usually you'd also install this globally with a -g option.

terminal
    
    $ npm install generator-angular
    
  

Now that we've got the generator installed, we can generate our app. Remember we've already cd'ed into our app folder, so the generator will actually derive the app name from the folder you are currently in.

terminal
    
    $ node_modules/yo/bin/yo angular
    
  

It asks us whether we would like to use bootstrap, which we do. We also want to use routes, so let's make sure to include that.

OK, so our app has been generated, now we use grunt to fire up our dev web server.

terminal
    
      $ grunt serve
    
  

Binding to data

We're going to pull down a JSON file then bind our view to it, so that we can play around with the generated app structure. OK, so in our main controller we want to use the $http service to pull down this JSON and bind to it. Now we set the result of the HTTP call on scope so that our view can access it.

app/scripts/controllers/main.js
    
      angular.module('budgetApp')
        .controller('MainCtrl', function ($scope, $http) {
          $http.get('/data/budget.json').success(function(result){
              $scope.budgetItems = result;
          })

        });
    
  

In our main.html view file, we get rid of all the copy. Now we do a simple binding to verify that the data is available.

app/views/main.html
    
      <div ng-repeat="item in budgetItems">
      {{item.description}}
      </div>
    
  

Here's some markup that utilises twitter bootstrap's table styles.

app/views/main.html
    
      <table class="table">
        <thead>
          <tr>
            <th>Description</th>
            <th>Amount</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          <tr ng-repeat="item in budgetItems">
            <td>{{item.description}}</td>
            <td>{{item.amount}}</td>
            <td>
              <a href="/#/detail/{{item.description}}">detail</a>
            </td>
          </tr>
        </tbody>
      </table>
    
  

Generating a custom route

We've got a detail link, but at the moment it doesn't work. This is because we haven't set it up yet. In terminal we use the angular generator through yo to generate a new route. This is where scaffolding really helps us out, it generates all the files necessary to serve the route: the controller, a test for the controller and a template for the route.

terminal
    
    $ node_modules/yo/bin/yo angular:route detail
    
  

It even hooked everything up for us in the routing definitions in the existing app.js file. This is really powerful. But, when we click on detail, it doesn't serve up the new route yet. That's because the route needs to be changed a bit to accommodate the description we're passing to it.

We quickly introduce a description token for the route here.

/app/scripts/app.js
    
    angular.module('budgetApp', [
      'ngResource',
      'ngRoute'
    ])
    .config(function ($routeProvider) {
      $routeProvider
        .when('/', {
          templateUrl: 'views/main.html',
          controller: 'MainCtrl'
        })
        .when('/detail/:description', {
          templateUrl: 'views/detail.html',
          controller: 'DetailCtrl'
        })
        .otherwise({
          redirectTo: '/'
        });
    });
    
  

Now when we click on the link, it serves up the view correctly.

We copy the code from the main controller in order to get the data. We set the correct item on scope to bind to. We use a standard filter call on the array to find an item with a matching description.

In order to get the description that was specified in the route, I need to let angular inject the $routeParams service. The routeParameters will have attributes for all the tokens that were specified in your routing definition. So we can expect an attribute called description to be available on it.

/app/scripts/controllers/detail.js
    
    angular.module('budgetApp')
      .controller('DetailCtrl', function ($scope, $http, $routeParams) {
        $http.get('/data/budget.json').success(function(result){
            $scope.item = result.filter(function(filterItem){
                return filterItem.description ===
                $routeParams.description;
            })
        })
      });
    
  

We can now use the browser back button to go back and click on another item. It's amazing how quickly you can get to this point with angular and yeoman.

Install a bower component

We've been using grunt to serve our app with live reload, we've used yo to generate our app and a new route. Let's get our hands dirty with bower and use it to pull in the very powerful underscore library which we'll use to display a total for our budget.

terminal
    
    $ node_modules/bower/bin/bower install underscore
    
  

I'm passing the --save option, because I would like bower to add this dependency to my bower.json file.

We need to instruct our index.html file to load underscore.

/app/index.html
    
      <script src="bower_components/underscore/underscore.js"></script>
    
  

Now that we have underscore available to us, we use it to calculate the total of all the items by doing a map first, and then a reduce to sum it up.

/app/scripts/controllers/main.js
    
    angular.module('budgetApp')
    .controller('MainCtrl', function ($scope, $http) {
      $http.get('/data/budget.json').success(function(result){
          $scope.budgetItems = result;

          $scope.total = _(result)
              .chain()
              .map(function(item){return item.amount;})
              .reduce(function(memo, num){
                  return memo + num;
              }, 0)
              .value();
      })

    });
    
  

Lastly we bind up our view to the newly created total.

/app/views/main.html
    
      <table class="table">
        <thead>
          <tr>
            <th>Description</th>
            <th>Amount</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          <tr ng-repeat="item in budgetItems">
            <td>{{item.description}}</td>
            <td>{{item.amount}}</td>
            <td>
              <a href="/#/detail/{{item.description}}">detail</a>
            </td>
          </tr>
          <tr>
            <td><h3>Total</h3></td>
            <td><h3>{{total}}</h3></td>
            <td></td>
          </tr>
        </tbody>
      </table>
    
  

Wrap up

And that's it for this screencast! Using angularjs with yeoman is a very powerful stack that does a lot of heavy lifting for us. I hope you've enjoyed this tagtree screencast.