Use $http once from Angular Service - angularjs

I need to grab some data from my db through an API and make it accessible throughout my Angular app. I understand that Services are good for storing data to be accessed from multiple controllers. However, in the following code I end up with a new $hhtp.get() each time just to get the same data.
Service:
.factory('Playlist', ['$http', function($http) {
var playlist = {};
playlist.getPlaylist = function() {
return $http.get('api/playlist.php')
.then(function (response) {
var data = response.data;
return data;
})
}
return playlist;
}])
Controllers:
.controller('ScheduleCtrl', ['$http', 'Playlist', function($http, Playlist) {
var self = this;
Playlist.getPlaylist()
.success(function(playlist) {
self.playlist_id = playlist.id;
fetchItems();
})
var fetchScheduleItems = function() {
return $http.get('api/schedule.php/'+self.playlist_id).then(
function(response) {
if (response.data === "null") {
console.log("No items");
} else {
self.items = response.data;
}
}, function(errResponse) {
console.error('Error while fetching schedule');
});
};
}])
.controller('PlaylistItemCtrl', ['$http', 'Playlist', function($http, Playlist) {
var self = this;
Playlist.getPlaylist()
.success(function(playlist) {
self.playlist_id = playlist.id;
fetchItems();
})
var fetchPlaylistItems = function() {
return $http.get('api/schedule.php/'+self.playlist_id).then(
function(response) {
if (response.data === "null") {
console.log("No items");
} else {
self.items = response.data;
}
}, function(errResponse) {
console.error('Error while fetching schedule');
});
};
}])
Is there a way to store the Playlist ID without pinging 'api/playlist.php' from every controller?
Update
Here's a Plunkr based on Abhi's answer: http://plnkr.co/edit/or9kc4MDC2x3GzG2dNeK?p=preview
As you can see in the console, it's still hitting the server several times. I've tried nesting CachedData.save() differently, but it doesn't seem to apply.

I would say store your data locally (CachedData factory - rename it to something that makes sense) and inside your getPlaylist method, before doing http call, check CachedData to see if your data is present and if not, then do the http call.
The code will be something like the below. I have just written it free-hand, so there may be some errors, but you get the picture.
.factory('Playlist', ['$http', 'CachedData', function($http, CachedData) {
var playlist = {};
playlist.getPlaylist = function() {
if (CachedData.data) {
// return cached data as a resolved promise
} else
return $http.get('api/playlist.php')
.then(function (response) {
var data = response.data;
cachedData.save(data);
return data;
})
}
return playlist;
}])
// CachedData factory
.factory('CachedData', function() {
var _data;
var cachedData = {
data: _data,
save: function(newData) {
_data = newData;
}
};
return cachedData;
})
EDIT: Also Remove fetchPlaylistItems from the controller and put it in a factory. The controller is just a glue between your viewmodel and view. Put all your business logic, http calls in a service.
EDIT: I have setup a plunk for you here. I hope it helps.
EDIT: John, the reason you are seeing two server calls is because they are from different controllers ManiCtrl1 and MainCtrl2. By the time, the getPlaylist method from MainCtrl2 is called, the first http request didn't get the chance to finish and save the data. If you add a timeout to MainCtrl2 before calling your service method, you will see that the data is retrieved from cache. See this updated plunk for a demo.
This will be more useful in an app with multiple views, where you don't want to reload data when navigating back to a view. (Ideally, depending on the type of data you are caching, you will have some expiry time after which you would want to reload your data).

You could do some pre validation when calling that method.
var playlist = {};
playlist.playlist = [];
playlist.getPlaylist = function () {
if (playlist.playlist.length <= 0) { //or some lodash _.isEmpty()
$http.get('api/playlist.php')
.then(function (response) {
playlist.playlist = response.data;
})
}
return playlist.playlist;
};
Hope it helps!

Related

Store AJAX result in factory and retrieve in controller

I am currently trying to store a $http JSON request to a variable in a factory and then retrieve the value of that variable in a controller.
Currently all I receive back is undefined. I imagine the AJAX request isn't finished running before the function gets called. I am new to Angular so trying to grab any basic concepts I can and helpful knowledge.
app.factory('typiCode', function($http) {
var jsonService = {
async: function() {
var promise = $http.get('https://jsonplaceholder.typicode.com/posts/1')
.then(function(response) {
console.log(response);
return response.data;
})
return promise;
}
};
return jsonService;
});
app.controller("getJson", function($scope, typiCode) {
$scope.returnedData = typiCode.jsonService;
$scope.logResults = function() {
var theData = $scope.returnedData;
console.log(theData);
}
});
<button ng-click="logResults()">Launch data</button>
Thank you in advance!
Your typiCode factory returns your jsonService object. So you should be calling typiCode.async() somewhere in your code.
You could do, for example, like this in your controller:
app.controller("getJson", function($scope, typiCode) {
typiCode.async()
.then(function(data) {
$scope.returnedData = data;
})
$scope.logResults = function() {
var theData = $scope.returnedData;
console.log(theData);
}
});

Angular JS: Service Response not Saving to Variable

I'm trying to work out why the response of this service isn't saving to $scope.counter. I've added a function to my service fetchCustomers(p) which takes some parameters and returns a number, which I'd like to save to $scope.counter.
service
angular.module('app')
.factory('MyService', MyService)
function MyService($http) {
var url = 'URL'';
return {
fetchTotal: function(p) {
return $http.get(url, { params: p })
.then(function(response) {
return response.data.meta.total;
}, function(error) {
console.log("error occured");
})
}
}
}
controller
$scope.counter = MyService.fetchTotal(params).then(function(response) {
console.log(response);
return response;
});
For some reason though, this isn't working. It's console logging the value, but not saving it to $scope.counter. Any ideas?
If I understand your question correctly, you're setting $scope.counter to a promise, not the response.
MyService.fetchTotal(params).then(function(response) {
// Anything dealing with data from the server
// must be put inside this callback
$scope.counter = response;
console.log($scope.counter); // Data from server
});
// Don't do this
console.log($scope.counter); // Undefined

ANgularJS sync server data with local data on request

There is one thing I can't understand in Angular - how better to sync API data with local data.
My structure object is very big, I just give you simple example.
I have 4 api links.
api_links: {
allCars: '/cars/',
cabrio: '/cars/cabrio',
sedan: '/cars/sedan',
mercedes: 'cars/sedan/mercedes'
}
And this is my local object to keep data locally
$rootScope.apiData = {
cars{
cabrio:{},
sedan: {}
}
}
In my single page app I want to reduce count of requests.
So on common page I'm recieving all Cars and put data to $rootScope.apiData
When I go to sedans page, I check if there any data in $rootScope.apiData.cars.sedan. If it's exist, I don't send request.
But if I start from sedans page, I recieve only sedans. Then when I go to common page - HOW can I check am I need to load more categories.
It's quick abstract example. But I try to find any good solutions or plugins for this and cannot.
Please help me!
UPDATE:
Here is my factory:
var api = angular.module('app.api', []);
api.factory('GetData', ['$http', '$q', '$rootScope', '$location'
function($http, $q, $rootScope, $location) {
var apiUrl = '/api/js/';
var apiMixes = {
dashBoard: 'api/dashboard',
accounts: 'api/accounts',
top: 'top',
logout: 'client/logout',
...
}
var methods = {};
methods.getApi = function(u, params) {
var url = apiMixes[u] + '?' + $.param( params );
var deferred = $q.defer();
// Here I need to check if I have cached data
cachedData(data) = ___TODO___;
if( cachedData(data) ){
deferred.resolve(cachedData(data));
// Turn watching on
$rootScope.$emit("receivedApiUpdate", cachedData(data));
return deferred.promise;
}
// If no - get gata from server and put to cache
$http.get(url, {cache: true}).success(function (res, status, headers, config) {
if(res && res.data){
// Turn watching on
$rootScope.$emit("receivedApiUpdate", res.data);
//LocalData.put(res.data);
deferred.resolve(res.data);
}else{
deferred.reject(status);
}
}).error(function (res, status, headers, config) {
(status==401) && $location.path('login/lock');
deferred.reject(status);
});
return deferred.promise;
};
methods.sendData = function(u, data, o) {
data = data || {};
o = o || {};
var deferred = $q.defer();
var url = '/api/js/'+ apiMixes[u];
$http.post(url, JSON.stringify(data)).success(function(res, status) {
if(res.success && res.data){
// Here I need to update my cache with some new value
o.localUpdate && ___PUT_TO_CACHE_TODO___;
deferred.resolve(res.data);
}
});
return deferred.promise;
};
return methods;
}]);
Here is my Controller
app.controller('MyAccountsCtrl', ["$rootScope", "$scope",
function ($rootScope, $scope) {
// Watcher for api updates
$rootScope.$on('receivedApiUpdate',function(event, response){
if(!response || !response.accounts) return;
renderData(response.accounts);
});
function renderData(accounts){
// This renders to template
$scope.accountList = accounts;
}
}]);
Here is mainCtrl, whick make common request, after it I need to update data in several templates.
app.controller('AppCtrl', ['$rootScope', '$scope', 'GetData',
function ($rootScope, $scope, GetData) {
// Here I fire different request for each page, I keep requests in router.
GetData.getApi( 'accounts' ).then(function(data){
// Show content when page is loaded
$('.main-content').removeClass('hidden');
},function(res){
log('GET DATA FAIL', res);
});
}]);
You need to create a service/factory for this, not use the $rootScope. Also, unless you need the data to be constantly updated, you can use the cache: true option inside your $http call.
angular
.module('app')
.factory('carFactory', carFactory);
function carFactory() {
var factory = {
getData: getData
}
return factory;
function getData(callback) {
$http({
method: 'GET',
url: '/cars',
cache: true
}).success(callback);
}
}
And then inside your controller/directives just inject the carFactory and use carFactory.getData(function(cars) { ... }) when you need the data. If the data doesn't exist, it will $http call for it and afterwards execute the callback function. If it does exist, it will return the data directly to the callback, without an $http call.

Have multiple calls wait on the same promise in Angular

I have multiple controllers on a page that use the same service, for the sake of example we will call the service USER.
The first time the USER.getUser() is called it does an $http request to GET data on the user. After the call is completed it stores the data in USER.data. If another call is made to USER.getUser() it checks if there is data in USER.data and if there is data it returns that instead of making the call.
My problem is that the calls to USER.getUser() happen so quickly that USER.data does not have any data so it fires the $http call again.
Here is what I have for the user factory right now:
.factory("user", function($http, $q){
return {
getUser: function(){
var that = this;
var deferred = $q.defer();
if(that.data){
deferred.resolve(that.data);
} else {
$http.get("/my/url")
.success(function(res){
that.data = res;
deferred.resolve(that.data);
});
}
return deferred.promise;
}
}
});
I hope my question makes sense. Any help would be much appreciated.
Does this work for you?
.factory("user", function($http, $q) {
var userPromise;
return {
getUser: function () {
if (userPromise) {
return userPromise;
}
userPromise = $http
.get("/my/url")
.then(function(res) {
return res.data;
});
return userPromise;
}
}
})
$http already returns the promise for you, even more, in 2 useful types: success & error. So basically, the option that #kfis offered does NOT catch errors and not flexible.
You could write something simple:
.factory('user', function($http) {
return {
getUser: function() {
$http.get('/my/url/')
.success(function(data) {
return data;
})
.error(function(err) {
// error handler
})
}
}
})

AngularJS service storing $http results to prevent requerying --- is there a better way to do this?

The Setting: I want to have a service that multiple controllers can query for data pulled using $http. The initial solution was to use promises as suggested here.
The Problem: Each time a controller queries the service, the service then returns an $http promise, resulting in multiple queries that just pulls the same data from a remote server, over and over again.
A Solution: The service function returns either data or a promise like below. And it is up to the controller to check and act accordingly.
app.factory('myService', function($http) {
var items = [];
var myService = {
getItems: function() {
// if items has content, return items; otherwise, return promise.
if (items.length > 0) {
return items;
} else {
var promise = $http.get('test.json').then(function (response) {
// fill up items with result, so next query just returns items.
for(var i=0;i<response.data.length;i++){
items.push(response.data[i]);
}
return items;
});
// Return the promise to the controller
return promise;
}
};
return myService;
});
So when a controller needs that data, the controller just does something like this:
app.controller('MainCtrl', function( myService,$scope) {
var promiseOrData = myService.async();
// Check whether result is a promise or data.
if ( typeof promiseOrData.then === 'function'){
// It's a promise. Use then().
promiseOrData.then( function(data ){
$scope.data = data;
});
} else {
// It's data.
$scope.data = data;
}
});
So the question is: Is there a better way of doing this? With many controllers, this method would have a lot of duplicate code. Ideally, the controllers will just query the service for data directly.
Thanks!
$http returns a promise, we can use that instead of creating a new one with $q. Once the promise is resolved, we can keep returning it.
.factory('myService', ['$http','$q', function($http, $q) {
var items = [];
var last_request_failed = true;
var promise = undefined;
return {
getItems: function() {
if(!promise || last_request_failed) {
promise = $http.get('test.json').then(
function(response) {
last_request_failed = false;
items = response.data;
return items;
},function(response) { // error
last_request_failed = true;
return $q.reject(response);
});
}
return promise;
},
};
}])
In your controller:
myService.getItems().then(
function(data) { $scope.data = data; }
);
Create your own promise that resolves to either the cached data or the fetched data.
app.factory('myService', function($http, $q) {
var items = [];
var myService = {
getItems: function() {
var deferred = $q.defer();
if (items.length > 0) {
//resolve the promise with the cached items
deferred.resolve(items);
} else {
$http.get('test.json').then(function (response) {
// fill up items with result, so next query just returns items.
for(var i=0;i<response.data.length;i++){
items.push(response.data[i]);
}
//resolve the promise with the items retrieved
deferred.resolve(items);
},function(response){
//something went wrong reject the promise with a message
deferred.reject("Could not retrieve data!");
});
}
// Return the promise to the controller
return deferred.promise;
};
return myService;
});
Then consume the promise in your controller.
app.controller('MainCtrl', function( myService,$scope) {
var promiseOrData = myService.getItems();
promiseOrData.then( function(data){
$scope.data = data;
},
function(data){
// should log "Could not retrieve data!"
console.log(data)
});
});

Resources