How to query and extract from server response in Angular - javascript

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.

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);
})

Two-way databind on a forEach?

I have an object of products. The products i get from a resource (Product), store in a factory (productsStore), and iterate through in a controller.
In another controller, i want to either empty of refresh products via productsStore.empty(); or productsStore.get();. But either of them does nothing. Meaning my forEach, below, is not being run again, and the list not updated.
What am i doing wrong?
Controller:
vm.products = productsStore.get();
vm.products.$promise.then(function (data) {
angular.forEach(vm.products, function (child) {
//. some code
)};
)};
Factory:
myApp.factory('productsStore', function ($http, $q, Product) {
var products = "";
var get = function () {
return products = Product.query();
};
var empty = function () {
return products = {};
};
// Bind products
products = get();
return {
get: get,
empty: empty
};
});
Resource:
myApp.factory('Product', function ($resource) {
return $resource('http://api.com/api/products/:id', { id: '#id' }, {
update: {
method: 'PUT'
}
});
});
try to loop through the data you get after the promise is resolved, otherwise it's a rather useless variable.
angular.forEach(data, function(child) {
//...do something with the child
});
Also, you have a typo, that I'm not sure you have in your actual code. In your controller, end of block should be }) and not )}

Angular service cache

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

AngularJS object losing value inside controller method

I'm a junior dev, so I might be missing something obvious, but I'm feeling a bit loony. I have a simple Angular webapp. I'm attempting to load a hash-dictionary of environment names that correspond to arrays of hosts. {development: ["dev.8090", "host.dev.9009"]} and then use that dictionary to find which host I'm currently on. I should be able to pass the location.host variable to the getEnv method and find the correlating key that will tell me which environment I'm in.
The dictionary loads, but when I try to access it inside of the getEnv method, it reverts to an empty object. Not undefined, mind you, but empty. Here's my code:
var app = angular.module('app', ['ngResource', 'ui.bootstrap', 'ui.router']);
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}]);
function AppController($scope, $http) {
window.MY_SCOPE = $scope;
$scope.env = "Local";
$scope.dict = {};
$scope.loadDict = function() {
$http.get('api/call/').
success(function(data){
for (env in data.environment) {
// data.environment = array of objects
// [
// {hosts: ["host1", "host2"], name: "string1"},
// {hosts: ["host1", "host2"], name: "string2"}
// ]
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
}
console.log($scope.envDict)
// in the console:
// Object {string1: Array[2], string2: Array[2]}
}).error(function(data){
console.error(data);
})
};
$scope.getEnv = function(host) {
for (key in $scope.dict) {
// never gets this far because $scope.dict is now = {}
for (value in $scope.dict[key]) {
if ($scope.dict[key][value] === host) {
$scope.env = key;
}
}
}
};
$scope.loadDict();
$scope.getEnv("host1");
}
I can manually call each of these methods and get the results I want from the console, using the MY_SCOPE variable. If I hard-code the dictionary, it works. If I console.log $scope.dict from anywhere in the code except from inside of the $scope.getEnv function, I get the result I expect. As soon as $scope.getEnv is involved, $scope.dict = {}.
I've tried hard-coding the keys into the dictionary. I've tried moving the definition around in the code. I've tried exporting the loadDict method into a factory. All to no avail. Ideas?
The $http.get call in $scope.loadDict is asynchronous. getEnv is getting called before your dictionary has been loaded. You need to call getEnv once that data has come back.
Have loadDict return the $http.getcall which will give you a promise. You can then chain on to that promise a success callback.
You should also put your $http calls in some sort of service to do it the 'angular' way :)
Try this instead:
$scope.loadDict = function() {
return $http.get('api/call/').
success(function(data){
for (env in data.environment) {
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
}
console.log($scope.envDict)
// in the console:
// Object {string1: Array[2], string2: Array[2]}
}).error(function(data){
console.error(data);
})
};
$scope.loadDict().then(function(result){
$scope.getEnv("host1");
}
Your problem is that you didn't deal with the fact that loadDict is async internally.
One way to solve this is to wait for it to complete by returning a promise from it and waiting for that promise to be resolved.
There are other ways to go about this, but this is probably one of the ways that is closest to what you already have:
// inject $q so you can make a promise
function AppController($scope, $http, $q) {
window.MY_SCOPE = $scope;
$scope.env = "Local";
$scope.dict = {};
$scope.loadDict = function() {
// set up the deferred response
var deferred = $q.defer();
$http.get('api/call/').
success(function(data){
for (env in data.environment) {
// data.environment = array of objects
// [
// {hosts: ["host1", "host2"], name: "string1"},
// {hosts: ["host1", "host2"], name: "string2"}
// ]
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
}
console.log($scope.envDict)
// in the console:
// Object {string1: Array[2], string2: Array[2]}
// all is well so resolve the promise
deferred.resolve();
}).error(function(data){
console.error(data);
// reject the promise
deferred.reject(data);
})
return deferred.promise;
};
$scope.getEnv = function(host) {
for (key in $scope.dict) {
// never gets this far because $scope.dict is now = {}
for (value in $scope.dict[key]) {
if ($scope.dict[key][value] === host) {
$scope.env = key;
}
}
}
};
$scope.loadDict().then(
function () {
$scope.getEnv("host1");
},
function (err) {
// whatever you want to do if the loadDict function failed to do its job
}
);
}
$scope.getEnv() is being called before $http.get() has returned data. You need to call $scope.getEnv() within the $http.get().success() block, like so:
$scope.loadDict = function() {
$http.get('api/call/').success(function (data) {
for (env in data.environment) {
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
}
$scope.getEnv("host1");
}).error(function(data){
console.error(data);
});
};
You need to treat things asynchronously . success is an asynchronous callback while getEnv is synchronous.
The solution in this case is to define a promise in loadDict and resolve it on success call.
Then , in the controller getEnv method you would write code after promise is resolved:
Roughly the code will be like this, I have not tested it, just wrote to give you idea:
$scope.loadDict = function() {
var deferred = $q.defer(); // to define a promise
$http.get('api/call/').
success(function(data){
deferred.resolve(data);//resolve the promise on success
}
}).error(function(data){
console.error(data);
})
return deferred.promise;//return promise
};
$scope.getEnv = function(host) {
$scope.loadDict().then(
function(data) {
for (env in data.environment) {
// data.environment = array of objects
// [
// {hosts: ["host1", "host2"], name: "string1"},
// {hosts: ["host1", "host2"], name: "string2"}
// ]
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
for (key in $scope.dict) {
// never gets this far because $scope.dict is now = {}
for (value in $scope.dict[key]) {
if ($scope.dict[key][value] === host) {
$scope.env = key;
}
}
}
});
};

angularjs $resource class-level callbacks, or post-processing

I have a $resource whose API will always return some data that needs to be cleaned up before going into the presentation layer. Specifically, it's .NET returning Date objects in the lovely '/Date(...)/' format.
I don't want to have to write a callback every time I call .query() or .get(). Is there some way to extend the resource with a callback that gets called upon REST methods that update the instance's properties, or by adding some sort of $watch that gets fired when the date property changes? Basically something that will happen for every instance of this $resource.
angular.module('myAppServices', ['ngResource'])
.factory('Participant', ['$resource', function ($resource) {
var res = $resource('api/url/participants/:id', { id: '#id' });
// This obviously doesn't work, but something kinda like this?
res.prototype.$watch(this.FieldName, function(newVal, oldVal) {
if (needsCleaning(newVal.fieldName) {
this.FieldName = cleanupField(newVal);
}
};
});
Ah-ha, I found a way around it and will leave it here. In version 1.1.2 they added support for passing all the $http.config options to a $resource. Naturally, the CDN I'm using doesn't have a recent enough version of angular-resource.js, but switching CDNs solved that.
I just used the transformResponse option to modify the data as it comes back.
angular.module('myAppServices', ['ngResource'])
.factory('Participant', ['$resource', '$http', function ($resource, $http) {
var res = $resource('api/url/participants/:id', { id: '#id' }, {
save: {
method: 'POST',
transformResponse: $http.defaults.transformResponse.concat([
function (data, headersGetter) {
data.FieldName = yourDateParsingFunction(data.FieldName);
return data;
}
])
}
});
I'm just adding my transformer on to $httpProvider's transformResponse, which will do all the deserialization, etc.
An easy way to do this is to overwrite the existing $resource methods you want to do post-processing on with your own. See the code and comments below for an example.
angular.module('myAppServices', ['ngResource'])
.factory('Participant', ['$resource', function ($resource) {
var res = $resource('api/url/participants/:id', { id: '#id' }, {
// create aliases for query and get to be used later
_query: { method: 'GET', isArray: true },
_get: { method: 'GET' }
});
// redefine the query method
res.query = function() {
// call the original query method via the _query alias, chaining $then to facilitate
// processing the data
res._query.apply(null, arguments).$then(function(res) {
var data = res.data;
// do any processing you need to do with data here
return data;
});
};
// redefine the method
res.get = function() {
// call the original get method via the _get alias, chaining $then to facilitate
// processing the data
res._get.apply(null, arguments).$then(function(res) {
var data = res.data;
// do any processing you need to do with data here
return data;
});
};
return res;
});
You'd use it the same way you're currently using Participant in your code, via Participant.query() or Participant.get(). The data you return in the chained $then handler will be used to resolve the promise returned by $resource.
The way I did it was by adding a service to the module:
angular.module('keeniolab', ['ngResource']).
factory('KeenIO',function ($resource) {
// factory implementation
}).service('KeenModel', function (KeenIO) {
var KeenSession = function () {
this.data = {};
};
KeenSession.prototype.fetch = function (query) {
var self = this;
KeenIO.get(query, function (result) {
self.data = result;
});
};
return new KeenSession();
});
Now you can simply monitor the collection:
$scope.$watchCollection(function () {
return KeenModel.data;
},
function (value) {
// code here
});
Keen.IO Resource Factory with Service Model

Categories