Angular and XML? No Problem!

This entry is part 7 of 9 in the series: AngularJS Learning Series

The Finished Product

JSON is a native object to AngularJS so working solely with JSON examples is easy. In the real world, however, you’ll probably need to deal with other types of data sources, like XML. Before I get too wordy, let’s start with what you’re really here for…the example:

[iframe src=”http://embed.plnkr.co/Hpx8GPHt0IqKADj8K1Qc” width=”100%” height=”365px”]

The Explanation

While the finished product looks like the result of 15 minutes of coding, I have to admit that it took me significantly longer to figure out. If you search the Angular Developer Guide or API for “XML” you’re going to come up woefully absent of useful results. Googling won’t get you much further. As frustration began to set in, I started thinking about what made loading an XML file more difficult than loading a JSON file. The answer is: not much!

Bridging the Language Gap

Angular likes JSON, and can you blame it? Angular is a JavaScript toolset and JSON is JavaScript Object Notation, a native brethren. XML, on the other hand, is tag-based markup…a foreign language to Angular. What I needed was a way to make Angular understand what XML was trying to communicate.

The key to overcoming any language gap is to have a translator. If there were a way to translate XML to JSON before handing it to the application then I could eliminate the language gap, and the problem.

A quick search for xml converters turned up multiple results. I chose Abdulla Abdurakhmano’s X2JS library because it was the first one I came across, it was lightweight, and it was pretty easy to use.

The code required to perform the translation is short and simple. Provide the library with XML, tell it which method to use, and receive JSON in return:

function(xml) {
 var x2js = new X2JS();
 var json = x2js.xml_str2json( xml );
 return json;
}

Where to Stick It

Now that I had a solution to the language gap I needed to decide where to implement it. Angular’s $http service conveniently applies transformation functions for both requests and responses. These functions apply transformations to data when it is sent and received, and they can be overridden.

Using the X2JS conversion code as a transformResponse function it was simple to get the $http service to read in XML and return JSON to the application, effectively creating the translator I needed. The results look something like this:

angular.module('myApp.service',[]).
    factory('DataSource', ['$http',function($http){
       return {
           get: function(callback){
                $http.get(
                    'data.xml',
                    {transformResponse:function(data) {
                    	// convert the data to JSON and provide
                    	// it to the success function below
						var x2js = new X2JS();
						var json = x2js.xml_str2json( data );
						return json;
						}
					}
                ).
                success(function(data, status) {
					// send the converted data back
					// to the callback function
                    callback(data);
                })
           }
       }
    }]);
    
angular.module('myApp',['myApp.service']);

var AppController = function($scope,DataSource) {
    
    //This is the callback function
    setData = function(data) {
        $scope.dataSet = data;
    }
        
    DataSource.get(setData);
    
}

That’s really all there is to it! The example above will retrieve an xml file named “data.xml”, convert it to JSON, and assign the results to $scope.dataSet.

The Caveat

XML comes in all shapes and sizes. Some use attributes, others don’t, and many use a combination of both. Some XML files use a proper header, others are just fragments. Your application will likely require tweaking to get the data into the shape that your application requires. Always read the docs, read the comments, and experiment.

The example at the beginning of this post is a great starting point. Fork it and experiment with different libraries or XML formats.

Keep on Coding!

Angular Intro to Modules, Services, Factories, and Filtering

This entry is part 5 of 9 in the series: AngularJS Learning Series

The Assignment

This week’s self-imposed Angular assignment was to create a small app that would dynamically filter a list of data using an input field as search criteria. I chose to base the app on my son’s homework assignment and use United States states and capitals.

In my previous examples I used Controllers for all functions and assignments. For simple non-critical applications the configuration works well, but for larger real-world applications a more abstract approach makes sense. Enter Angular Modules.

The Basics

Modules initialize Angular applications and wire together its services, filters, directives, etc. This makes the code portable, easier to debug, and easier to unit test.

To keep things simple, I only used a service in this example. The service is a gofer, responsible for carrying out any requests made by the controller. The controller sets scope properties and behaviors and handles calls from the template.

Here’s a basic module, service, and controller in Angular:

angular.module('myApp.service',[]);
angular.module('myApp',['myApp.service']);
var StatesController = function($scope) {}

On the first line is a service named ‘myApp.service’. Note that the dot is not necessary, nor is using the app name as a prefix. I could have named it ‘gofer’ if I wanted. The important part is that I let the main application module know the name, which brings me to line 2, the main module.

The main module names the app and gives it a a comma delimited reference to services, filters, etc. that can be referenced. If I defined a filter named ‘myApp.filter’ my module definition would have been: “angular.module(‘myApp’,[‘myApp.service’,’myApp.filter’]);”. The module name, “myApp”, will be used in my ngApp directive in the template.

Line 3 is the shell of a controller definition which will be directly accessible by the template. While it wouldn’t do anything at this point, we could hook those three lines up to a template and assign myApp to the ngApp directive without error.

Let’s Get Some Data!

As with most applications, we’ll need access to data. My data source is a JSON file that looks like this:

[
    {"name":"Alabama", "capital":"Montgomery","abbreviation":"AL"},
    {"name":"Alaska", "capital":"Juneau","abbreviation":"AK"},
    {"name":"Arizona", "capital":"Phoenix","abbreviation":"AZ"},
    {"name":"Arkansas", "capital":"Little Rock","abbreviation":"AR"},
    ...
]

I need a way to create a resource object out of that data so I can interact with it. Angular’s ngResource module provides exactly what I need in its $resource service.

NOTE: The ngResource module is NOT in the base angular.js file. To use it you must load angular-resource.js in addition to angular.js. If you get an error in your debugging console that says “Uncaught Error: No module: ngResource”, chances are you forgot to load angular-resource.js. You do use a developer tools, right?

To retrieve the resource I utilized a method used to create objects in Angular called a factory. When I put the factory method inside my service, and then provide my Controller with a reference to it, I can use it in my controller to set a scoped property, like this:

angular.module('myApp.service',['ngResource']).
    factory('States', function($resource){
        return $resource('states.json', {},{
            get: {method: 'GET', isArray:true}
        });
    });

angular.module('myApp',['myApp.service']);

var StatesController = function($scope,States) {
    $scope.stateData = States.get();
};

So, what changed? First, I gave my service a reference to Angular’s ngResource module. That will give me the ability to use the $resource service for my factory allowing me to interact with my data source, “states.json”. Notice that the factory has a name, “States” which I provide to my Controller. From there I can access actions inside the factory, such as “get”, “save”, and “delete”.

Now, if I create a template referencing the controller I can display the data using my stateData property. Check out the source code here:
[iframe src=”http://embed.plnkr.co/zXGpgCkUl8cF5rzbIWYP” width=”100%” height=”300″]

Make Something Angulary Happen

We now have a working module, a service, a factory, a controller, and data. Time to do something slick with it. Building off the html above, Inside my StatesController DIV I’m going to delete the current {{stateData}} dump and replace it with something a little easier to read:

<ul class="states">
   <li>
      <div class="resultwrapper">
         <span class="title">{{state.name}}</span><br>
         <span class="bold">Abbreviation:</span> {{state.abbreviation}}<br>
         <span class="bold">Capital:</span> {{state.capital}}
      </div>
   </li>
</ul>

Using ng-repeat and an unordered list, my state information now displays in a style-able list. Unfortunately, a couple of my states are not in alphabetical order. This can be fixed easily by telling the repeater to order it using a built-in filter module named orderBy after my iterator:

<ul class="states">
   <li ng-repeat="state in stateData | filter:criteria | orderBy:'name'">
       <div class="resultwrapper">
          <span class="title">{{state.name}}</span><br>
          <span class="bold">Abbreviation:</span> {{state.abbreviation}}<br>
          <span class="bold">Capital:</span> {{state.capital}}
       </div>
   </li>
</ul>

Note that ‘name’ is a string, not a reference. I could have also defined a property for it in the controller and used it with the orderBy.

Now that they’re in alphabetical order I want to be able to search them. This was done by adding a simple input and assigning it an ng-model of ‘critera’, then giving that to the ng-repeat filter. Angular uses the value entered into the criteria field as a filter for the stateData array. Throw in a style sheet and you have a pretty nifty widget using a very minimal amount of code.

The Result

[iframe src=”http://embed.plnkr.co/BixOgwEu66Hzco6rZJqt” width=”100%” height=”500″]