Mihai Marinescu
Mihai Marinescu

Reputation: 779

Angularjs: load data in service then load it to controllers

I've checked several solutions on the web but I don't quite understand how to stop the controllers from loading. So, I've created a plunkr to highlight what I want to do.

Basically: I want to load all data in a service and then pass around that data from that service to each controller. When the app first loads, because it's Async, the controllers are loaded first.

I could have just used the factory in each controller, but I want to hold that data in the "allProducts" property of the service. I don't see the need to call the factory function each time a view loads.

In the example, I've also tried with the $q service, but seems to me it has the same behaviour just like the http from the factory and still needs to call the http request on each view load...

So, could somebody help me with this example and implement an elegant solution?

app.factory('productsFactory', ['$http', '$q',
    function($http, $q) {
        var cachedData; //here we hold the data after the first api call  
        function getData(callback) {
            if (cachedData) {
                callback(cachedData);
            } else {
                $http.get('http://api.bestbuy.com/v1/products(longDescription=iPhone*|sku=7619002)?show=sku,name&pageSize=15&page=5&apiKey=bqs7a4gwmnuj9tq6bmyysndv&format=json')
                    .success(function(data) {
                        cachedData = data; //caching the data in a local variable
                        callback(data);
                    });
            }
        }

        return {
            getProds: getData
        }
    }
])

app.service('appService', ['productsFactory', '$q',
    function(productsFactory, $q) {
        var _this = this;
        productsFactory.getProds(function(data) {
            _this.allProducts = data; //wait for data to load before loading the controllers
        })

    }
])

app.controller('productsCtrl', ['$scope', 'appService',
    function($scope, appService) {
        $scope.myProducts = appService.allProducts;
    }
]);

plunkr here: http://plnkr.co/edit/ZvtYwXHSasC3fCAZKkDF?p=preview

Upvotes: 1

Views: 4592

Answers (3)

Mihai Marinescu
Mihai Marinescu

Reputation: 779

FINALLY!!! I Managed to do what I was looking for.

Note, this is not necessarily a best practice, but it was a problem that was bugging me and wanted to know how it's done.

The way I did it was to create a global variable, where I use a main resolve function to wait for the factory to do the http get and then pass it to the service. Then, I use resolve on every state where I need that data and reference that function.

UPDATE: Realized that I was calling the same factory function each time the state was changins, so I decided to go with a variable - a property in the appService which turns to true when the http get was called once: appService.retrieved and changed the main resolve function a bit.

var mainResolve = ['$q', 'appService', 'productsFactory', function($q, appService, productsFactory) {
var defer = $q.defer();
if(appService.retrieved) {
    defer.resolve();
} else {
  productsFactory.getProds(function(data) {
      appService.allProducts = data;
      defer.resolve();
  })
  appService.retrieved = true;
}

return defer.promise;
}]

And in the state

.state('home', {
  url: "/home",
  templateUrl: "home.html",
  controller: 'homeCtrl',
  resolve: {
    waitingFor: mainResolve
  }
})

You can find the plnkr with the working solution, here: http://plnkr.co/edit/ZvtYwXHSasC3fCAZKkDF?p=preview

Again, the factory could be refactored some more and eliminate some code like the caching data. This way, we only go once to the factory function.

Upvotes: 0

Tanase Butcaru
Tanase Butcaru

Reputation: 992

Check this: http://plnkr.co/edit/ey0na3l2lyT0tUdbDyrf?p=preview

Changes:
- a main app controller that wraps all your app.
In this controller we prevent route change if the boot hasn't finished all its jobs. When boot is finished, we change location to the main/default route.
- in our run block we set the bootStatus var to false and wait until products are fetch.

Also, I've stored the result from service to $rootScope so you can use that data in all your controllers without injecting the service over and over again.

Upvotes: 1

Alexandru R
Alexandru R

Reputation: 8833

I didn't actually test it, but looks like you need to create and return a promise in order to have the data returned when it's available.

    app.factory('productsFactory', ['$http', '$q',
    function($http, $q) {

        var cachedData; //here we hold the data after the first api call  
        function getData(callback) {
var d = $q.defer();
            if (cachedData) {
d.resolve(callback(cachedData));

            } else {
                $http.get('http://api.bestbuy.com/v1/products(longDescription=iPhone*|sku=7619002)?show=sku,name&pageSize=15&page=5&apiKey=bqs7a4gwmnuj9tq6bmyysndv&format=json')
                    .success(function(data) {
                        cachedData = data; //caching the data in a local variable
                        d.resolve(callback(cachedData));
                    });
            }
return d.promise;
        }

        return {
            getProds: getData
        }
    }
])

app.service('appService', ['productsFactory', '$q',
    function(productsFactory, $q) {
        var _this = this;
        productsFactory.getProds(function(data) {
            _this.allProducts = data; //wait for data to load before loading the controllers
        })

    }
])

app.controller('productsCtrl', ['$scope', 'appService',
    function($scope, appService) {
        $scope.myProducts = appService.allProducts;
    }
]);

Upvotes: 2

Related Questions