1

I have the following controller in my application, but there is some strange behaviour that I cannot explain. I've numbered two of the lines to help with the description, they don't both exist at the same time in the live code.

var app = angular.module('movieListings', ['ngResource', 'ngRoute', 'ui.bootstrap', 'ng']);

var cachedMovieList = []; 

//Controller for movie list
app.controller('MovieListController', ['$http', function($http){
    var mlc = this; //needed for the $http request
    this.movies = cachedMovieList;    
    this.loaded = false;
    this.error = false;


    if(this.movies.length == 0) {            
        console.log("Grabbing new movie list from DB");
        $http.get('data/movies.json').success(function(data){
            mlc.movies = data; 
            mlc.loaded = true;
            cachedMovieList = data; //(1)
        }).error(function(data){
            mlc.error = true;
        });
        cachedMovieList = this.movies; //(2)
    } else {
        this.loaded = true;        
    }
}]);

With the code as above with line (1) present and line (2) not present, I am able to cache the result so that when I flick between pages I don't need to constantly re-get the data.

However if I remove line (1) and insert line (2), the variable "cachedMovieList" is never populated. I would expect it to be based on the fact that "mlc.movies" was assigned to... but I cannot understand why this is the case?

Any advice welcome.

Syx
  • 485
  • 3
  • 10
  • Use a factory or service for caching. They are already singletons ands shared across all controllers. – Michael Kang Jun 24 '14 at 13:26
  • Thank you, I was attempting this before but getting hung up on the 'scope' without realising the real cause of the problem. Your example made everything crystal clear with the factory approach and I'll be sure to use it in the future :) – Syx Jun 24 '14 at 14:39

4 Answers4

0

If I've understood this correct, you're entering the if condition only when this.movies.length == 0. In such a case, this.movies will be null, so cachedMovieList would get populated with a null value.

Chirag Bhatia - chirag64
  • 4,430
  • 3
  • 26
  • 35
0

Because (2) probably gets executed first before the $http.get() request is finished. $http.get() is an AJAX request.

If you want to cache, you might want to use $cacheFactory instead :)

srph
  • 1,312
  • 17
  • 35
  • Thanks, this comment opened my eyes to the asynchronous execution of the $http.get() function! – Syx Jun 27 '14 at 22:31
0

I believe you are mistaking the live updation of values that happens in view to live updation that would happen with variable assignments. Your line 2 will set cachedMovieList to [] initially. I believe that is quite obvious. But you think that since callback updates this.movies that change would cascade to cachedMovieList. That won't happen as you are re-assigning the mlc.movies variable that means it refer to new variable instead of modifying existing value.

If you really want to make you logic work, please update mlc.movies variables like following

mlc.length = 0 // Empty the array
mlc.push.apply(mlc, data);

Please check following answer for more information

How do I empty an array in JavaScript?

Community
  • 1
  • 1
amitamb
  • 1,067
  • 11
  • 22
0

Implement a factory that retrieves the data. Use angular.copy to preserve the array reference when the data returns from the $http call.

 var app = angular.module('movieListings', ['ngResource', 'ngRoute', 'ui.bootstrap', 'ng']);
 app.factory('movies', function($http) {
     var movies = {
         data: [],
         loaded: false,
         error: false
     };

     $http.get('data/movies.json').success(function(data){
        angular.copy(data, movies.data); 
           movies.loaded = true;
        }).error(function(data){
           movies.error = true;
        });
     return movies;

 });

Inject the factory into your controller:

//Controller for movie list
app.controller('MovieListController', ['$scope','movies', function($scope, movies){
     this.movies = movies;
}]);

Factories (like services) are singletons. They are initialized once, and cached for the entire lifetime of the SPA.

Use the controller in the view:

 <div ng-controller="MovieListController as ctrl">
       <div ng-show="!ctrl.movies.loaded"> Loading... </div>
       <ul>
            <li ng-repeat="movie in ctrl.movies.data">
                {{ movie.name }}
            </li>
       </ul>
 </div>
Syx
  • 485
  • 3
  • 10
Michael Kang
  • 52,003
  • 16
  • 103
  • 135
  • `var loaded = true` seems odd as a definition, as it's not completed the request at this time. However when changing it to `false`, the value change is not passed through to the controller when the factory is loaded? This seems odd as when it's set to `true` the view loads and then updates moments later with the data from the `$http.get`. Am I missing something? – Syx Jun 24 '14 at 15:53
  • Sorry, you are right. It was a typo - I will correct. – Michael Kang Jun 24 '14 at 16:00
  • Quick note as I tripped up on this when using another factory - in the controller definition section `['$scope','movies', function($scope, movies)`, the first `'movies'` refers to the name of the factory, and the second `movies` refers to the `var` inside it! – Syx Jun 24 '14 at 18:25