Build your first AngularJS application to search iTunes

AngularJS, arguably one of the best (and enjoyable!) MV(C*) client-side frameworks can help bring clarity to your Javascript life, while enabling powerful applications to leverage all of those wonderful restful services you have built in your favorite Rails (okay, other languages too) app. Because of the structure it encourages, and the ever popular 'separation of concerns', it will help guide you to Javascript sanity.

In this blog, I'm going to provide a quick and dirty intro on AngularJS. Keep in mind, this blog is an introduction to Angular with a fast example to show the ease at which a dynamic data-driven website can be built. There are many more extensive tutorials which dive deep on each AngularJS component, but this blog should wet your appetite for all that is Angular and have you off and running!

Angular's Components

Controllers are the one of the central components of the Angular framework. As the name would dictate, they are in control of a section of your application. They are responsible for defining a data model, populating that model with data (from one of those web services, for example) and exposing it by binding it to a view.

Directives and Views are the way in which data and functionality are presented to the user. Views, as you might guess, show data, collect data from users, and provide abilities to interact with your application. By design, the base of all views is just an HTML page. The way in which views are afforded the interactive capability is through directives. Directives are statements which trigger AngularJs to provide its magic and they thoroughly documented in the AngularJS API. They are the glue which ties together the models with the view. If you want a model updated with newly entered data, you can use a directive to do just that- automagically! Want to iterate over a collection of data from one of your controllers and build a list? There's a directive for that too. Depending on the magic you want, you have many directives from which to choose. You just add these directives to your html elements, include AngularJS's javascript file, and off you go!

Services, in web development, are typically thought of something as a feature of an application server. You spawn a request and receive a corresponding response. In AngularJS, you can think of services a something slightly different- a component that performs a duty or job. The job might be trivial such as providing random numbers, or it might be something complex like providing the means to communicate back to a typical web service. AngularJS provides a multitude of services which you can leverage, as well as allowing you to build your own. Services are the grab bag of functionality that don't quite belong in a controller or model. Instead, they are separated out on their own to be leveraged by either, and even directives.

Routing provided by AngularJS allows you easily manage the flow of traffic as users click around your AngularJS app. Because complex AngularJS apps are going to have multiple controllers, being able to abstract away the navigation rules from the views and into a routing table is going to help reduce confusion and make it clear which controller and view handles what and when.

In a nutshell, those are the building blocks of any AngularJS app, but lets see some code to see how all of this works.

Here's our example view that we will enable to search iTunes for an artist's name and return the first 50 tracks. I've started off with just including the AngularJS Javascript file via the google apis CDN. You could download these files and store them within your container as well.

<html>  
 <head>
  <title>AngularJS iTunes</title>
  <script 
   src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.min.js">
  </script>
 </head>
 <body></body>
</html>  
  • First thing we need to do is use a directive to trigger AngularJS into action. Directives are scoped, so its magic starts wherever you direct it to start. The directive to trigger AngularJS into app action is ng-app. In this example, I have directed AngularJS to start app'ing at the html DOM element, but you could start it anywhere you want.
<html ng-app>  
 <head>
  <title>AngularJS iTunes</title>
  <script 
   src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.min.js">
  </script>
 </head>
 <body></body>
</html>  
  • So we have our app instantiated. Lets update the view to now use a controller to search the iTunes service for an artist or band. I have used a bunch of different directives in this one view to handle the form, submit the data, read the data, and render html elements from that data. The beauty of how all of this works is that you decide how the view looks IN THE VIEW. This keeps you from having to decide how the view looks in Javascript. Gone are the days of manipulating the DOM from jQuery. I have added, at the top, the itunes_controller.js script include which will hold the controller code we are about to write.
<html ng-app>  
    <head>
      <title>AngularJS iTunes</title>
      <script
     src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.js">
      </script>
      <!-- the script which will contain the iTunesController -->
      <script src="itunes_controller.js"></script>
    </head>

    <!-- use the ng-controller directive to scope 
         this body to iTunesController -->
    <body ng-controller="iTunesController">

        <!-- show an error if there's a search error -->
        {{ error }}
        <!-- add form, use ng-submit directive to call 
             searchiTunes, pass the artist -->
        <form name="search" ng-submit="searchiTunes(artist)">

         <!-- Artist/band name required, use the ng-model 
              directive to pass the artist name back into the
              controller/model -->
         <input type="search" required placeholder="Artist" ng-model="artist"/>
            <input type="submit" value="Search"/>
        </form> 
        <!-- ng-show directive to decide when to show results. 
             In this case, we will show the songs when there 
             are songs to show. -->
        <table ng-show="songs">
            <thead>
                <tr>
                   <th>Album Artwork</th>
                   <th>Track</th>
                   <th>Preview</th>
                </tr>
            </thead>
            <tbody>
                <!-- ng-repeat directive to loop over each 
                     song in the songs list. -->
                <!-- The song data is loaded into objects for 
                     easy access. Just call on the attributes 
                     you need. -->
                <tr ng-repeat="song in songs">
                    <!-- ng-src lets you load imgs when data 
                         is present. -->
                    <td><img ng-src="{{song.artworkUrl60}}" 
                         alt="{{song.collectionName}}"/> 
                    </td>
                    <td>{{song.trackName}}</td>
                    <td><a href="{{song.previewUrl}}">Play</a></td>
                </tr>
            </tbody>
        </table>
    </body>
</html>  
  • We have our page ready to go. Now we will write the iTunesController which will provide this view with all of the data, as well as receive the data through the ng-model directive from the form. I have commented this code inline to describe what each function does.
// defined in itunes_controller.js
// ensure the controller gets the view's scope and the http service.
var iTunesController = function($scope, $http){

    // define the search function called by the form.
    $scope.searchiTunes = function(artist){
        // use the jsonp callback function from the $http service this
        // will get around any limitations for cross-domain scripting.
        $http.jsonp('http://itunes.apple.com/search', {
            params: {
                'callback': 'JSON_CALLBACK',
                'term': artist
            }
        // returns a promise. when returned, .then perform action..
        }).then(onSearchComplete, onError)
    }

    // Get the data out of the response when search succeeds.
    var onSearchComplete = function(response){
        // the response has a few data elements
        // so we will grab all of that.
        $scope.data = response.data
        // we will also store just the songs into
        // a separate variable for the view to iterate
        $scope.songs = response.data.results
    }

    // if there's an error, store that for viewing.
    var onError = function(reason){
        $scope.error = reason
    }
}
  • This function defines the iTunesController which depends on Angular's $scope and $http service. It contains the definition for the searchiTunes service, as well as a couple handlers for successful search and errors. A successful search will populate $scope variables, used by the view, to render the search results. It doesn't have any knowledge of the view (as designed) and doesn't care what the view does with the data. It concentrates on getting data and designing the model of it. The view? That's the other guy's problem.

  • Once you save the results of this file, and refresh your html page, you should be at this point:

  • And when you provide a band name and click search, you should see something like this for your band, as I did for Soundgarden.

This blog covered a lot of AngularJS concepts. But there's one other thing I want to cover lest you run off and start coding AngularJS for production applications. You will notice that the controller was defined in the global namespace (one function just sitting out there). For trivial examples or practice work, this is perfectly acceptable. But for actual development, you will want to encapsulate those controllers into modules. I'm going to briefly discuss modules now.

Modules in AngularJS is a way to contain your code and keep it out of the global namespace. You would probably group together controllers that make sense being together; or if they all belonged together they might be in the same module. You can have multiple modules in the same project, containing controllers in a manner that makes sense for your application. The main takeaway is that you avoid poluting the global namespace.

Creating a module:

var app = angular.module('itunesSearch', []);  

And that's it. You call the module function off the single global Angular variable, 'angular', pass it a name for the module and an array of dependencies. For our iTunes search app, we have no dependencies, so the array is empty.

Adding your controller to the module:

// Adds iTunesController by name and passes the function which creates it.
`app.controller("iTunesController", iTunesController);`

Invoke your module, exposing your controllers in the page:

<body ng-app**="itunesSearch"**>  

Put it all together and update your controller code to: 

  1. Not be in the global name space.
  2. Build a module.
  3. Add a controller to the module.
  4. Update ng-app to use the module.
<html ng-app**="itunesSearch"**>  
    <head>
      <title>AngularJS iTunes</title>
      <script 
       src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.js">
      </script>
      <script src="itunes_controller.js"></script>
    </head>
    ....
</html>  
// itunes_controller.js
// wrap code in Immediately Invoked Function Expression (IIFE)
(function() {
    // create the itunesSearch module, no dependencies
    var app = angular.module("itunesSearch", [])

    var iTunesController = function($scope, $http, $log){

        var onSearchComplete = function(response){
            $scope.data = response.data
            $scope.songs = response.data.results
        }

        var onError = function(reason){
            $scope.error = reason
        }

        $scope.searchiTunes = function(artist){
            $http.jsonp('http://itunes.apple.com/search', {
                params: {
                    'callback': 'JSON_CALLBACK',
                    'term': artist
                }
            }).then(onSearchComplete, onError)
        }
    }

    // add the iTunesController to the itunesSearch module
    app.controller("iTunesController", iTunesController)
}());

By wrapping the code into an IIFE block, the code is executed (immediately), and the module and controller are ready for consumption by our Angular app. In practice, you would probably move the module creation line into its own file (along with other modules that are created), and would then reference that module in this code by doing this:

// move to app.js file, for example
var app = angular.module("itunesSearch", [])  
// in itunes_controller, change the line above to reference,
// instead of create, the module, by removing the (empty) dependency array:
var app = angular.module("itunesSearch")  
// consolidating modules into one file may also help reduce 
// confusion and provide clarity

From this simple, yet powerful example, you should have an insight into how the structure of Angular provides sanity to your applications along with its robust capabilities.

AngularJS Website AngularJS API

Questions or comments? @leechris [email protected]