Angular service cache - javascript

I'm trying to cache response from $http into an object for a session in angular, so once the initial call has been made, every other call to service.getCategories() (e.g), will get the data from the object rather than to the api.
The service is being resolved at the route, but there is authentication, which will redirect to another route - calling service.getCategories() again.
I'm attempting this by setting an init variable on call, then all other calls will direct to the populated object - but it seems to reset the service somehow, and the returned object gets populated twice, so there's double of everything. See below:
var $ = require('jquery');
module.exports = angular.module('app.common.services.category', [])
.factory('categoryService', ['$http', '$q', '$rootScope', function($http, $q, $rootScope) {
// API Parameters
var deferred = $q.defer();
// Services
var Categories = {
init: false,
categories: [],
index: 0,
// Retrieve all data on load.
// Loaded into an array for all other services
// to use after init.
getCategories: function(page) {
if(!Categories.init) {
$http.get('api/core/get_category_index')
.then(function(result) {
var data = result.data.categories;
$.each(data, function(i, category) {
category.index = Categories.index;
Categories.categories.push(category);
Categories.index++;
});
Categories.init = true;
return deferred.resolve(Categories.categories);
});
// Return promise once catgories is resolved
return deferred.promise;
} else {
return Categories.categories;
}
},
allCategories: function() {
return Categories.categories;
}
}
return Categories;
}]);

A problem with your approach is when the service function getCategories is called for the second time, the first time server request may not is resolved, causing a second call to the server. So you should move the init flag directly after the function call getCategories.
An other problem is that in your case you don't know whether the function will return a promise or an Array. I Suggest always returning an Array
module.exports = angular.module('app.common.services.category', [])
.factory('categoryService', ['$http', '$q', '$rootScope', function($http, $q, $rootScope) {
// API Parameters
var deferred;
// Services
var Categories = {
categories: [],
index: 0,
// Retrieve all data on load.
// Loaded into an array for all other services
// to use after init.
getCategories: function(page) {
if(!deferred) {
// replacement for intit flag
deferred = $q.defer();
$http.get('api/core/get_category_index')
.then(function(result) {
var data = result.data.categories;
$.each(data, function(i, category) {
category.index = Categories.index;
Categories.categories.push(category);
Categories.index++;
});
deferred.resolve(Categories.categories);
});
}
// always return a promise
return deferred.promise;
},
allCategories: function() {
return Categories.categories;
}
}
return Categories;
}]);
Maybe you can return the service itself with the promise. Then you could write everywhere something like:
myService.load().then(
function success(theService) {
theService.allCategories()
}
);
Now it doesn't matter anymore whether the service was loaded before or not

Related

How to pass param from controller to service in AngularJs

I'm currently working on a project to help me better understand angularjs! I am currently stuck on how to pass a parameter from the controller to service.
In my program, I have created a function called "GetForecastByLocation" when a user types in an input clicks on a button. From there I want to take their input and then pass it to the http call in service.js.
Originally, $http.get was in a long giant string of the API url, but I googled around and it seems that I'm supposed to use parameters when trying to change a portion of the string. As of right now, I know parameter is hardcoded to a specific city, but I want to take new input and pass the value of vm.city to the $http.get call.
If any one can help I would greatly appreciate it. Thank you!
controller.js
var app = angular.module('weatherApp.controllers', [])
app.controller('weatherCtrl', ['$scope','Data',
function($scope, Data) {
$scope.getForecastByLocation = function(myName) {
$scope.city = myName;
Data.getApps($scope.city);},
Data.getApps(city)
.then(function(data)){
//doing a bunch of things like converting units, etc
},
function(res){
if(res.status === 500) {
// server error, alert user somehow
} else {
// probably deal with these errors differently
}
}); // end of function
}]) // end of controller
service.js
.factory('Data', function($http, $q) {
var data = [],
lastRequestFailed = true,
promise;
return {
getApps: function() {
if(!promise || lastRequestFailed) {
promise = $http.get('http://api.openweathermap.org/data/2.5/weather?',{
params: {
q: Tokyo,
}
})
.then(function(res) {
lastRequestFailed = false;
data = res.data;
return data;
}, function(res) {
return $q.reject(res);
});
}
return promise;
}
}
});
Passing arguments to a factory method is no different than passing arguments to a plain old function.
First, set up getApps to accept a parameter:
.factory('Data', function($http, $q){
// ...
return {
getApps: function(city){
promise = $http.get(URL, {
params: {q: city}
}).then( /* ... */ );
// ...
return promise;
}
};
});
Then pass it your argument:
$scope.getForecastByLocation = function(myName) {
$scope.city = myName;
Data.getApps($scope.city);
}
It's just like setting a value to a function's context variable.
Services.js
Simple example of a service.
.factory('RouteService', function() {
var route = {}; // $Object
var setRoute_ = function(obj)
{
return route = obj;
};
var getRoute_ = function()
{
if(typeof route == 'string')
{
return JSON.parse(route);
}
return null;
};
return {
setRoute: setRoute_,
getRoute: getRoute_
};
})
Controllers.js
Simple example of Service usage:
.controller('RoutesCtrl', function ($scope, RouteService) {
// This is only the set part.
var route = {
'some_key': 'some_value'
};
RouteService.setRoute(route);
})

angular factory for $http service makes call twice

I created an angular factory for $http service. I am getting the response and able to use the same in the controller but the problem is, when i check the network tab in the browser, the http request is made twice
Factory:
app.factory('myService', function ($http, $q) {
var deferred = $q.defer();
var responseData = null;
var obj = {};
obj.getData = function(){
$http.get('test.json').success(function(response){
responseData = response;
deferred.resolve(responseData);
}).error(function(response){
deferred.reject(responseData);
});
return deferred.promise;
}
obj.myData = function(){
return responseData;
}
return obj;
});
Controller:
app.controller('myController', function($scope,myService){
myService.getData().then(function(){
$scope.myDetails = myService.myData();
});
});
what's wrong in my approach. Please provide me a solution
The way you are making your caching scenario is quite complicated and not really helpful. How do you know if data has already been loaded?
Maybe you can create a simple Caching Service to handle your caching at a single point (nr of code lines will go down).
angular.module("YourApp").factory("CachingService", [
"$q",
"$http",
function ($q, $http,) {
var cache = {};
return {
getFromCache: getFromCache
};
function getFromCache(url) {
var deferred = $q.defer();
if (cache[url]) {
deferred.resolve(cache[url]);
} else {
return $http.get(url).then(function (result) {
cache[url] = result;
return result;
});
}
return deferred.promise;
}
}
]);
And then, you simply call it inside your other service :
angular.module("YourApp").factory("myService", [
"CachingService",
function(CachingService){
return {
getData: getData
};
function getData(){
return CachingService.getFromCache("test.json");
}
}
]);
And then, inside your controller :
app.controller('myController', function($scope,myService){
myService.getData().then(function(result){
$scope.myDetails = result.Data;
});
});
you can return $http not deferred.promise

Angular service issue -- returning an array in a service containing promises

As you can see this is my first time attempting this and I appear to be doing it incorrectly. I just want to take some code, consisting of promises and http requests, and put it in a service before the controller uses it. My goal is to simply clean up the controller so it doesn't contain all of that code.
After logging it in the last step of the controller the object appears as undefined. Also, all the requests are being made successfully. So, it's jumping through all the hoops fine so I'm guessing it must not be returning any value in the service and nothing gets passed on to the subsequent function in the controller. How can I return the 'people' array in the service after the promises have been fulfilled?
var myApp = angular.module('myApp', []);
myApp.controller('AppCtrl', function ($scope, $http, dataService) {
var getPeople = function () {
return $http.get('/getpeople');
};
getPeople().then(function (response) {
dataService.compilePeople(response)
})
.then(function (people) {
console.log(people);
$scope.people = people;
});
});
myApp.service('dataService', function ($q, $http) {
this.compilePeople = function (response) {
var people = [];
names = response.data;
grandPromiseArray = [];
names.forEach(function (index) {
var name = index,
count = $q.defer(),
skills = [],
urls = '/getskillsbyname/' + name,
urlc = '/getcountbyname/' + name;
grandPromiseArray.push(
$q.all([$http.get(urls), $http.get(urlc)])
.then(function (response) {
people.push({
name: name,
skills: response[0].data,
count: response[1].data
});
})
);
});
return $q.all(grandPromiseArray).then(function () {
return people
});
}
});
You need to return the promise from compilePeople() in order for the people to be passed into the next .then() handler. so close ;)
getPeople()
.then(function (response) {
//You were missing this return
return dataService.compilePeople(response)
})
.then(function (people) {
console.log(people);
$scope.people = people;
});

How to get the asynchronous data in my case

I am trying to set a service and want the controllers in my app get the data from the service.
I have something like
angular.module('myApp').service('testService', ['Product',
function(Product) {
var products
//Product is a $resource object to send an http request
Product$.query({
id: 123
}, function(object) {
setProducts(object);
});
var setProducts = function(object) {
products = object;
}
var getProducts = function() {
return products;
}
return {
setProducts: setProducts,
getProducts: getProducts
};
}
]);
in my another controller
angular.module('myApp').controller('productController', ['$scope', 'testService',
function($scope, testService) {
//return undefined...
console.log(testService.getProducts())
}
]);
//testService.getProducts() returns undefined.
I think the reason is because I am making $http request and it's asynchronous so the testService has no idea what the product is when the app first loads. How do I fix this issue? Thanks so much!
I use a promise "q$" to deal with asynch calls:
angular.module('myApp').service('testService', ['Product',
function(Product, $q) {
var products
var setProducts = function(object) {
products = object;
}
var getProducts = function() {
var deferred = $q.defer();
//Product is a $resource object to send an http request
Product$.query({
id: 123
}, function(object) {
setProducts(object);
deferred.resolve(object);
});
return deferred.promise;
}
return {
setProducts: setProducts,
getProducts: getProducts
};
}
]);
angular.module('myApp').controller('productController', ['$scope', 'testService',
function($scope, testService) {
//use a promise
testService.getProducts().then(function(data){
console.log(data);
},
function (error) {
console.log(error);
})
}
]);
The promise has two call backs one for complete and one for error. You can deal with the errors in your view model as needed.

How to query and extract from server response in Angular

I want to create a find method that loops through an array returned by the $resource service in Angular.
If I have a service like so:
'use strict';
angular.module('adminApp').factory('ProductType', function($resource) {
var ProductType;
ProductType = $resource('http://localhost:3000/api/v1/product_types/:id.json', {
id: '#id'
}, {
update: {
method: 'PUT'
}
});
ProductType.find = function(typeName){
var types = this.query(),
typeObject = {},
self = this;
for(type in types) {
var result = types[type],
resultName = self.normalizeName(result.name),
if(typeName === resultName) {
typeObject = result;
}
}
return typeObject;
};
return ProductType;
});
I tried wrapping it all in a function and returning the function thinking it had something to do with it being async and I also tried nesting a callback in the query method but that just allowed me to modify the response and not actually return anything differently.
When I try and set the return value to $scope in the controller I get a blank object
The this.query() method would return an array which might not be filled until the this.query() method has got its results back from the server. You will need to do something like this to wait until the call to the server has completed. As this is sort of async you will need to return a promise from this method that is resolved when the initial query has completed and you have searched the results.
'use strict';
angular.module('adminApp').factory('ProductType', [
'$q',
'$resource',
function($q, $resource) {
var ProductType;
ProductType = $resource('http://localhost:3000/api/v1/product_types/:id.json', {
id: '#id'
}, {
update: {
method: 'PUT'
}
});
ProductType.find = function(typeName) {
var defer = $q.defer(),
types = this.query(),
self = this;
types.$promise.then(function () {
var result,
resultName,
typeObject,
type;
for(type in types) {
result = types[type];
resultName = self.normalizeName(result.name);
if(typeName === resultName) {
typeObject = result;
break;
}
}
defer.resolve(typeObject);
}, function (err) {
// the called failed
defer.reject(err);
})
return defer.promise;
};
return ProductType;
}]);
Taken from the angular docs https://docs.angularjs.org/api/ngResource/service/$resource
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data.

Categories