18 Dec 2013, 09:30

Sailing app - part 3 - AngularJS does not only fetch content, it can transform too

In part 1, I cheated to provide a JSON file AngularJS could directly interact with but as I told you, it came from an MS Excel file I converted into a CSV one. We can in fact avoid this XLS > CSV > JSON conversion and make the CSV to JSON conversion on the fly when application is loaded.

For today, we'll need :

  • The CSV version of the file : app/data/index-essais-215.csv
  • A library to transform the CSV file to JSON ; I quickly chose JQuery-CSV as it made the job even if it add JQuery as a dependancy (which is already used for the project somewhere as it is defined in bower.json file). Files will be stored in app/vendor/jquery-csv.

As all pre-requisites are satisfied, let's update our code :

In app/index.html, add the jquery-csv at the end, before </body> :

    <!-- CSV parser and JQuery as pre-requisites -->
    <script type="text/javascript" src="https://nicolas.steinmetz.fr/post/2013/12/18/vendor/jquery-csv/jquery.csv-0.71.min.js"></script>

In app/scripts/controller/index.js :

  • We'll change the $http.get() shortcut for a more complex http call. It will introduce the "transformResponse" method and the cache property.
  • As previously, this is made asynchronously
  • Once the file is fetched, we go into the transformResponse section in which we will transform the CSV file into a JSON array.
angular.module('vmCollectionApp')
  .controller('IndexCtrl', ['$scope', '$http', function ($scope, $http) {
    $http({
      method: 'GET',
      url: "./data/index-essais-215.csv",
      transformResponse: function(issuelist) {
        // Transform CSV file into a JSON object
        var json = $.csv.toObjects(issuelist);
        return json;
      },
      cache: true,
    })
    .success(function(issuelist, status) {
        $scope.issuelist = issuelist;
    })
    .error(function(data, status) {
      $scope.issuelist = issuelist || "Request failed";
    });

    $scope.orderProp = 'model';   
  }]); 

Almost perfect, still our test to update , ie in test/spec/controllers/index.js :

  • We need to change only the beforeEach section in which the file was fetched and fake the content of the CSV file
  // Initialize the controller and a mock scope
  beforeEach(inject(function (_$httpBackend_, $controller, $rootScope) {
    // Define the expect answer from the http request and content of the json file supposed to be fetched
    var issuelist = 'model,boat_type,category,issue,month,year\n \
      420,dériveur,présentation,11,novembre,1996 \n \
      420,dériveur,une occasion mise à nu,11,novembre,1996';

    // Initiate httpbackend service to get the json file in asynchronous mode and to fake the $http service
    $httpBackend = _$httpBackend_;
    $httpBackend.expectGET('./data/index-essais-215.csv').respond(issuelist);
   
    scope = $rootScope.$new();
    IndexCtrl = $controller('IndexCtrl', {
      $scope: scope
    });
  }));

If you run grunt test at this step, it... fails !

Indeed, you need to make karma (the test runner) aware of jquery and jquery-csv. For that, just edit karma.conf.js in the project root directory. You need to update the files section with the jquery declaration and the app/vendor "global" one :

    // list of files / patterns to load in the browser
    files: [
      'app/bower_components/angular/angular.js',
      'app/bower_components/angular-mocks/angular-mocks.js',
      'app/bower_components/angular-resource/angular-resource.js',
      'app/bower_components/angular-cookies/angular-cookies.js',
      'app/bower_components/angular-sanitize/angular-sanitize.js',
      'app/bower_components/angular-route/angular-route.js',
      'app/bower_components/jquery/jquery.js',
      'app/scripts/*.js',
      'app/scripts/**/*.js',
      'app/vendor/**/*.js',
      'test/mock/**/*.js',
      'test/spec/**/*.js'
    ],

And we're done. grunt test should pass now and we have the same features as at the end of part 2.

Idea of this part was to show you that you don't strictly need JSON objects/array with AngularJS. You only need a ressource you can fetch and that you can "manipulate". Some other use cases could be the consumption of XML, RSS feeds, etc.

Of course, you can grab the code and see the results.