Angular JS persisting data across controller instances

Where do we want to store our data in Angular JS and how does it flow through your web/mobile app. This is especially important if you are using Angular JS in the mobile context. Recently I’ve been working on some Ionic Framework apps. Here is my example.

The user leaves the page then navigates back. We are duplicating API calls and losing the users context within the data. Behind the scenes we did this:

  1. Initialize controller A
  2. Load data set A into controller A via API call
  3. Load page B
  4. Initialize controller A
  5. Load data set A into controller A via API call

Alternatively we could have done this:

  1. Initialize controller A
  2. Load data set A into service A via API call
  3. Pass data from service A to Controller A via a promise
  4. Load page B
  5. Initialize controller A
  6. Load cached data set A from service A

An example of a controller and service pairing might look like this (NOTE: This is specific to my Ionic Cordova mobile app, but you can get the idea. Also note that the more function returns a promise):

.controller('HotCtrl', function($scope, UtilService, HotService) {
 UtilService.trackGAView('Hot');
 $scope.util = UtilService;
 $scope.movies = HotService.all();

 $scope.doRefresh = function() {
   HotService.clear();
   $scope.movies = [];
   setTimeout(function() {
     $scope.$broadcast('scroll.refreshComplete');
     $scope.moreMovies();
   }, 700);
 };

 $scope.moreMovies = function() {
   $scope.util.loading(true);
   HotService.more().$promise.then(function(){
     $scope.movies = HotService.all();
     setTimeout(function() {
       $scope.$broadcast('scroll.infiniteScrollComplete');
     }, 200);
     $scope.util.loading(false);
     $scope.hasMore = HotService.hasMore();
   });
 };
})

.factory('HotService', ['$resource', 'MovieService', 'UtilService', function($resource, MovieService, UtilService) {
 var page = 1;
 var movies = [];
 var regions = UtilService.getRegions();
 var hasMore = true;
 
 return hotObj = {
   all: function() {
     if(regions !== UtilService.getRegions()) {
       regions = UtilService.getRegions();
       hotObj.clear();
     }
     return movies;
   },
   more: function() {
     options = {page: page, n: 'hot', per_page: 500};
     return MovieService.query(UtilService.getParams(options), null, function(response, headers){
       if(response.length < 1) {
         hasMore = false; 
       } else {
       hasMore = true;
     }
     page++;
     angular.forEach(response, function(value, key) {
       movies.push(value);
     });
     },
       function(response) {
     });
   },
   get: function(index) {
     return movies[index];
   },
   hasMore: function(){
     return hasMore;
   },
   clear: function() {
     page = 1;
     movies = [];
     hasMore = true;
   }
 }
}])

Angular JS reusable controller functions

So this problem initially started when I found myself repeating similar functions across controllers. I also wanted to be able to access these functions from the view, which is why they are attached to the $scope. Notice both controllers contain the same function. I suppose we could attach this function to the $rootScope but that sounds messier and harder to test. Also referencing that from the view would not be as clean.

<a href="" ng-click="linkTo('https://www.digitalocean.com/?refcode=0fb20044d22d')" />

app.controller('MoviesCtrl',['$scope', function($scope){
  $scope.linkTo = function(link) {
    window.open(link, '_system');
  };
});

app.controller('TvShowsCtrl',['$scope', function($scope){
  $scope.linkTo = function(link) {
    window.open(link, '_system');
  };
});

My preferred solution is to this is

<a href="" ng-click="util.linkTo('https://www.digitalocean.com/?refcode=0fb20044d22d')" />

app.factory('UtilService', function() {
  return {
    linkTo: function(link) {
      window.open(link, '_system');
    }
  }
});

app.controller('MoviesCtrl',['$scope', 'UtilService', function($scope, UtilService){
  $scope.util = UtilService;
});

I like to use a utility service like this dry out my controllers. This was especially useful in my most recent Ionic Cordova app.