AngularJS and loading data over HTTP

When creating an AngularJS controller that depends on some data that needs to be loaded over HTTP it’s really tempting and easy to just inject the $http service into your controller and load the data from there. And that is exactly what I did with the moviesListCtrl. But easy and convenient doesn’t make it the best way to do so!

 

Services: a better approach to HTTP

I strongly believe that a controller, regardless of AngularJS, MVC or WebAPI, should contain as little logic as possible. It  should just be the spider in the web and delegate to others when something needs to be done. In this case the work is loading data over HTTP and instead of doing that itself the controller should defer to some other service that is focused on just that.

 

Single Page Applications and state

It turns out there is another reason to separate the loading of data from the controller. A controller is reloaded each time the route changes. And if the controller loads the data that implies that the data will be reloaded on each navigation action. And indeed that was the case, the movies where reloaded each time the moviesListCtrl was reactivated. Now with some pretty dynamic data that might be exactly what you need but with something as static as a movie collection there is little point. And I would argue that in most applications that also applies to the majority of the data collections.

 

Creating a movies service

Creating a movie service is pretty easy. AngularJS has different ways of doing so but most of the time I use a factory. See this StackOverflow answer for more information about the different options. I basically moved the relevant code from the controller into a separate service and expose the required functions.

   1: module.factory("moviesSvc", function($http, $q) {


   2:     var movies = [];


   3:     var page = 0;


   4:     var genres, director;


   5:  


   6:  


   7:     function nextPage() {


   8:         var defer = $q.defer();


   9:  


  10:         var queryStr = "";


  11:         if (genres.length) {


  12:             queryStr = "&genres=" +


  13:                 genres


  14:                     .map(encodeURIComponent)


  15:                     .join("&genres=");


  16:         }


  17:  


  18:         if (director) {


  19:             queryStr += "&director=" + encodeURIComponent(director);


  20:         }


  21:  


  22:         $http.get("/api/movies?page=" + page + queryStr).then(function(e) {


  23:             [].push.apply(movies, e.data);


  24:             defer.resolve(!!e.data.length);


  25:         });


  26:         page++;


  27:  


  28:         return defer.promise;


  29:     }


  30:  


  31:     function query(genres1, director1) {


  32:         if (director !== director1)


  33:             movies.length = 0;


  34:  


  35:         genres = genres1;


  36:         director = director1;


  37:  


  38:         if (!movies.length) {


  39:             page = 0;


  40:             nextPage();


  41:         }


  42:  


  43:         return movies;


  44:     }


  45:  


  46:     return {


  47:         query: query,


  48:         nextPage: nextPage


  49:     };


  50: });

 

This greatly simplifes the controller making both much easier to develop and test.

   1: module.controller("moviesListCtrl", function($scope, $location, $window, genres, moviesSvc, director) {


   2:     $scope.loadingData = false;


   3:  


   4:     $scope.movies = moviesSvc.query(genres, director);


   5:  


   6:     $scope.nextPage = function() {


   7:         $scope.loadingData = true;


   8:  


   9:         moviesSvc.nextPage().then(function(newMoviesLoaded) {


  10:             $scope.loadingData = !newMoviesLoaded;


  11:         });


  12:     };


  13:  


  14:     $scope.filterByGenre = function(genre) {


  15:         genres.push(genre);


  16:         $location.path("/movies/genres/" + genres.join(","));


  17:     };


  18:  


  19:     $scope.showMoviePoster = function() {


  20:         var width = ($window.innerWidth > 0) ? $window.innerWidth : $window.screen.width;


  21:         return width > 767;


  22:     };


  23:  


  24:     $window.onresize = function() {


  25:         $scope.$digest();


  26:     };


  27: });


  28:  



 



Try it



The running application can be found here and the source on GitHub here.



 



Conclusion



Separating the HTTP requests from the controller makes code much easier to develop and test. And the side benefit is that the singleton nature of AngularJS services make it much easier to cache data and produce a faster and more efficient application.



 



Index of posts on the RAW stack



See here for more posts on the RAW stack.

One thought on “AngularJS and loading data over HTTP

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>