Chaining angularJS requests - angularjs

I'm looking for a simple example to follow where I use the response of a request to initiate additional GET requests.
I have 2 services - one returns a list of search results objects including an ID - a second which takes this ID and returns an image.
For example:
myApp.factory('Search', ['$resource', function($resource){
return $resource('/api/search/', {}, {
getResults: {method:'POST'}
});
}]);
myApp.factory('Image', ['$resource', function($resource){
return $resource('/api/image/', { id: '#id' }, {
get: {method:'GET'}
});
}]);
How can update my controller to use the output JSON of the first call to retrieve the images?
myApp.controller('myAppController', function($scope, Search, Image) {
$scope.searchResults = Search.getResults();
});
Thanks.

You can chain promises (which is what resources is working on) by either nesting your calls or have the previous success method return a promise and then hatch onto that.
Depending on your scenario, this could be a simple solution:
Search.getResults(function (results) {
var imageId = results[0].imageId; // don't know what you need
$scope.images = Image.get({id: imageId});
});

Related

How do I edit data called from Angular API request, before is NG-Repeated?

first Stack Overflow post!
I'm trying to get my head around Angular, i've used jQuery for years, but im having requests in work to use it.
I'm loading in external JSON through a HTTP reqest..
$http({
method: 'GET',
url: "/Api/News/GetNews?pageNumber=" + pageNumber
}).then(function successCallback(response) {
$scope.myData = response.data
}, function errorCallback(response) {
showError()
});
}
This is working fine and the content is displaying in my NG-Repeat..
<div class="newsRow" ng-repeat="x in myData track by $index">
<div class="col-md-3">
Read more
</div>
<div class="col-md-9">
<h3>{{x.Title}}</h3>
<p>{{x.Excerpt}}/p>
{{x.NewsItemVersion}}
</div>
</div>
The only issue is the news ID (NewsItemVersion) needs to be formatted before it is displayed, it needs to be rounded to an integer.
How do I intercept this value and change it before it is displayed?
I would suggest to separate part of retrieving data and modifying. You can create 2 services for that and one controller to pass data to view.
e.g
Here is controller, it knows only about newsService and does not care about parsing id or whatever, just load.
app.controller('myController', ['$scope', 'newsService', function($scope, newsService) {
$scope.pageNumber = 1;
newsService.loadNews(pageNumber)
.then(function(news) {
$scope.news = news;
}, function(err) {
console.log(err);
})
}]);
Your service responsible for processing news and preparing them for controller or any other further usage. It knows what kind response will come and how to process it. In your case you can go through news list and call parseInt for all NewsItemVersion. So you have only one place where you modify it.
app.factory('newsService', ['$q', 'requestService', function($q, requestService) {
return {
loadNews: function(pageNumber) {
var defer = $q.defer();
requestService.getNews(pageNumber)
.then(function(data) {
data.forEach(function(item) {
item.NewsItemVersion = parseInt(item.NewsItemVersion, 10);
});
defer.resolve(data);
}, function(err) {
defer.reject(err);
});
return defer.promise;
}
}
}]);
And finally requestService or call whatever you want :) httpService, backendService etc. It is very thin layer which knows how and where to send request in order to get information from backend.
app.factory('requestService', ['$http', '$q', function($http, $q, requestService) {
var urls = {
'getNews': '/Api/News/GetNews?pageNumber=%pageNumber%'
};
return {
getNews: function(pageNumber) {
var requestUrl = urls['getNews'].replace('%pageNumber%', $pageNumber);
var defer = $q.defer()
$http({
method: 'GET',
url: requestUrl
}).then(function(response) {
defer.resolve(response.data);
}, function() {
defer.reject('Some meaningful message');
});
return defer.promise;
}
}
}]);
You have two main options here. Either you transform the data directly, as in the above answer of A Macdonald, or you use a filter.
If you're sure that you will only need the transformed data in your app, and you'll never need the original floating number, I'd suggest going with that first option. (I just noticed your comment on that answer, saying you can't seem to affect the $scope variable at all. If this is the case, I suggest you create a plunker or show us a bit more of your implementation, as this really should work.)
If you only need the rounded number for display purposes, and will still use the original floating number in you're logic, there are two approaches: You can create an additional property on the $scope called, for example, roundedNewsItemVersion. Or you can create a custom Angular filter. This is a really powerful feature of the language which you can use to transform any data, using a simple pipeline.
angular.module('yourApp', []).filter('absNumber', function() {
return function(input) {
// Optionally check wether input is a number, etc...
return Math.abs(input);
};
});
You can then use this filter in your code like this:
<div class="col-md-9">
<h3>{{x.Title}}</h3>
<p>{{x.Excerpt}}/p>
{{x.NewsItemVersion | absNumber}}
</div>
With custom filters, you can easily transform any data in your view, without actually changing the data itself. Of course, this filter is completely modular and you can continue to use it on different locations throughout your app. Now I know all this might be a bit of an overkill, but I hope it gives you some more insight into what exactly makes AngularJS a powerful framework.
By the way, I'd also suggest placing any asynchronous code that deals with getting data like your $http request in a service, instead of a controller. Controllers in Angular should only be used to add stuff to the $scope, not perform any meaningful logic.
handle it within the callback for the ajax call:
$http({
method: 'GET',
url: "/Api/News/GetNews?pageNumber=" + pageNumber
}).then(function successCallback(response) {
response.data.forEach(function (item) {
item.NewsItemVersion = Math.abs(item.NewsItemVersion);
});
$scope.myData = response.data
}, function errorCallback(response) {
showError()
});
}
Thanks everyone for the answers. I found the simplest answer was to use the forEach function as advised by A Macdonald, it essentially created a new data array with the manipulated content in it. I think this is the more jQuery type way of doing it, but it works for me.
$http({
method: 'GET',
url: "/Api/News/GetNews?pageNumber=" + pageNumber
}).then(function successCallback(response) {
var newsItems=[]
angular.forEach(response.data, function(value, key) {
newsItems.push({
Title : response.data[key].Title,
Excerpt : response.data[key].Excerpt,
Image : response.data[key].Image,
Url : response.data[key].Url,
NewsItemVersion : Math.abs(response.data[key].NewsItemVersion)
})
});
$scope.myData = newsItems;
}, function errorCallback(response) {
showError()
});
}
This method is useful as it allows the manipulation of the data before it is rendered out, so you could add variables, counters etc to the data before it is displayed.

How can I modify the results of $resource in Angular from two different REST APIs

I have two REST APIs I need to use: One from Mongolab for development purposes and one from the actual API that is not accessible at the moment. The problem is that the ID is handled a bit differently in these and the object structure differs. Mongo uses the object._id.$oid notation and the actual API object.ID notation. The Mongolab resource is:
app.factory('Items', function ($resource) {
var items = $resource('https://api.mongolab.com/api/1/databases/x/collections/items/:id',
{
apiKey:'x',
id:'#_id.$oid'
}
});
return items;
});
And the query call (currently using):
$scope.items = Items.query({}, function () {
if (API == 'Mongo') {
angular.forEach($scope.items, function(item) {
item.ID = item._id.$oid;
});
};
});
I want to be able to easily switch the different APIs without modifying the code in every query call or link (I have dozens of calls and links with resource IDs). So I want to move the API == 'Mongo' check to upper level: I tried to use the forEach ID altering directly in the factory where I create the Items resource but it doesn't work that way. How can I modify the results directly before the results are populated through query ?
Or should I just create different branches for different APIs?
I think you can just add two distinct factories, and query the one you need depending on the value of "API" variable.
app.factory('ItemsApi', function ($resource) {
var items = $resource('https://api.mongolab.com/api/1/databases/x/collections/items/:id',
{
apiKOey:'x',
id:'#_id.$oid'
}
});
return items;
});
app.factory('ItemsMongo', function ($resource) {
var items = $resource('https://api.mongolab.com/api/1/databases/x/collections/items/:id',
{
apiKey:'x',
id:'#id'
}
});
return items;
});
Then in the controller (instead of in the resource factory) you could use:
if (API == 'Mongo')
$scope.items = ItemsApi.query();
else
$scope.items = ItemsMongo.query();
UPDATE:
If this is what you currently have, then you may want to consider adding an additional property to each element in the returned array. You can do this by means of overriding the default factory query() method and then iterating over each element adding a duplicate ID field. Check this out:
$resource('https://../:id',{id: '#id'}, {
query: {
isArray: true,
method: 'GET',
params: {},
transformResponse: function (data) {
var wrapped = angular.fromJson(data);
angular.forEach(wrapped, function(item) {
--do something to wrapped items --
});
return wrapped;
}
}
transformResponse and angular.forEach should do the trick

How to communicate with server using AngularJS within Google Apps Script

Recently it has become possible to use angularjs within google apps script via the iframe sandbox mode.
My problem comes when trying to communicate with the server (gapps spreadsheet) and receiving asynchronous data in return.
The implementation for receiving data from the server is to use a function with a callback function like so:
google.script.run.withSuccessHandler(dataGatheringFunction).getServerData();
getServerData() would be a function that resides server-side that would return some data, usually from the accompanying spreadsheet. My question is how to use the callback function within the parameters of AngularJS. A typical $http function could be placed in a provider, and the scope value could be populated after then.() returns. I could also invoke $q. But how would I deal with the necessity of google's callback?
Here's a simplified version of what I'm messing with so far:
app.factory("myFactory", function($q){
function ssData(){
var TssData = function(z){
return z;
}
google.script.run.withSuccessHandler(TssData).getServerData();
var deferred = $q.defer();
var d = deferred.resolve(TssData)
console.log("DP: " + deferred.promise);
return deferred.promise;
}
return ssData();
})
Then in the controller resolve the server call similar to this:
myFactory.then(set some variables here with the return data)
My question is simply - How do I deal with that callback function in the provider?
The script throws no errors, but does not return the data from the server. I could use the old $timeout trick to retrieve the data, but there should be a better way.
You only need to $apply the output from the server function:
google.script.run.withSuccessHandler(function(data) {
$scope.$apply(function () {
$scope.data = data;
});
}).withFailureHandler(errorHandler).serverFunction();
Maybe the most elegant solution that makes sure the google.script.run callbacks are registered automatically in the AngularJS digest cycle would be to use the $q constructor to promisify the google callbacks. So, using your example above:
app.factory('myFactory', ['$q', function ($q){
return {ssData: ssData};
function ssData(){
var TssData = function(z){
return z;
};
var NoData = function(error) {
// Error Handling Here
};
return $q(function(resolve, reject) {
google.script.run
.withSuccessHandler(resolve)
.withFailureHandler(reject)
.getServerData();
}).then(TssData).catch(NoData);
}
}]);
Then in your controller you can call myFactory.ssData()
Since I don't know exactly what TssData is doing I included it here but note that this simply returns another promise in this context which you will still have to handle in your controller:
myFactory.ssData().then(function(response) {
// Set data to the scope or whatever you want
});
Alternately, you could expose TssData by adding it to the factory's functions if it is doing some kind of data transformation. If it is truly just returning the response, you could refactor the code and omit TssData and NoData and handle the promise entirely in the controller:
app.factory('myFactory', ['$q', function ($q){
return {ssData: ssData};
function ssData(){
return $q(function(resolve, reject) {
google.script.run
.withSuccessHandler(resolve)
.withFailureHandler(reject)
.getServerData();
});
}
}]);
app.controller('myController', ['myFactory', function(myFactory) {
var vm = this;
myFactory.ssData()
.then(function(response) {
vm.myData = response;
}).catch(function(error) {
// Handle Any Errors
});
}]);
An excellent article about promises (in Angular and otherwise) is here: http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
This guy seems to be pulling data from a GSheet into angular quite happily without having to do anything fancy.
function gotData(res) {
$scope.validUser = res.validUser;
var data = angular.copy(res.data), obj, i=0;
Object.keys(data).forEach(function(sh) {
obj = {title: sh, checked: {}, showFilters: false, search: {}, sort: {index: 0, reverse: false}, currentPage: 0, checkedAll: true, showBtns: true, searchAll: ''};
obj.heading = data[sh].shift();
obj.list = data[sh];
obj.heading.forEach(function(s,i) {
obj.checked[i] = true;
});
$scope.sheets.push(obj);
});
$scope.sheets.sort(function(a,b) {
return a.title > b.title ? 1 : -1;
});
$scope.gotData = true;
$scope.$apply();
}
google.script.run.withSuccessHandler(gotData).withFailureHandler($scope.gotError).getData();
My solution was to get rid of the $q, promise scenario all together. I used $rootScope.$broadcast to update scope variables from the server.
Link to spreadsheet with script.

Accessing and using JSON within an Angular service for logic flow

I asked the wrong question yesterday (and got a goodanswer that worked), but am realizing it's not what I needed. I need to be able to retrieve JSON data (preferably once), store it, and access it throughout my service. The challenge I'm having is that all the examples I can find talk about using JSON and passing to the app/controller, whereas in this case I need to get it, check it, and then it dictates what my module/service does.
For instance, I have my App and Controller, and then I have a module such as (this is psuedo-code, not meant to run):
angular.module("myModule")
.service("myService1", function($q, myService2, $http) {
this.getModel = function() {
return {
title: "My Title",
desc: "My Desc"
options: function () {
if (condition A)
return "option1";
else
return "option2";
}
};
};
})
.service("myService2", function($q, $http) {
this.getCfgInfo = function () {
var defer = $q.defer();
$http.get("my/json/url").then(function(response) {
defer.resolve(response.data);
});
return defer.promise;
};
})
In this example, I'm wanting to get the JSON, and use it within myService1 for both literal values (title, desc) as well as for conditions (condition A within the if).
I know I can do something like this (thanks to Joel for helping yesterday):
service("myService1", function($q, myService2, $http) {
// get a promise object for the configuration info
var cfgProm = rtDataMapper.getCfgInfo()
this.getModel = function() {
return {
title: cfgProm.then(function(response) {
return response.JSON_NAME;
}),
and it works fine as I've got the title mapped back into my model and there is a watch(), but I'm stumped as to how I get, store, and use the JSON within the service itself as a conditional (i.e. if (condition A) where condition A is coming from the JSON. Trying to wrap these in .then() doesn't seem to make sense, or at least I can't figure out how to do it.
I'm new to Angular and am attempting to modify some code that was left to us. I'm guessing I don't need the myService2 just to get the JSON. Can anyone help point me in the right direction? I've spent several hours online but can't seem to find a relevant reference/example.
Thanks
Live demo (click).
I'm having the service immediately get the data when it is injected (that code will only run once no matter how many times you inject it). That's nice because you won't have to call a function to get the data - it's called for when creating the service.
Your service method that returns that data will need to return the promise of the data, of course, since you aren't guaranteed that it will have come through when you ask for it. You can pass arguments to that method to use to determine your conditions. All you need to do for that is use promise.then in the method and resolve the promise with the modified data. Since that method is returning the promise already, the modification will be updated on the resolve. See all of this below and in the demo.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
myService.getData(15).then(function(data) {
$scope.myData = data;
});
});
app.factory('myService', function($q, $timeout) {
//this code only runs once when you first inject the service
//get data immediately
var deferred = $q.defer();
$timeout(function() { //simulate ajax call
var data = { //ajax response data
foo: 15,
bar: 'Some data!'
};
data = modifyData(data, 1);
deferred.resolve(data);
}, 500);
function modifyData(data, fooVal) {
if (data.foo === fooVal) {
data.baz = 'Conditional data!';
}
return data;
}
var myService = {
//data can be modified when it comes from the server,
//or any time you call this function
getData: function(fooVal) {
if (fooVal) { //if you want to modify the data
deferred.promise.then(function(data) {
data = modifyData(data, fooVal);
deferred.resolve(data);
});
}
return deferred.promise;
}
};
return myService;
});

AngularJS $http url placeholders not being placed with real data

I am new to Angular, and am having an issue where my $http url placeholders are not being replaced by the actual data. What I want is for this:
"/json/:searchType/:userId"
To be replaced with this when I use my $http call:
"/json/user/123"
Instead, I get something like this, according to firebug's Net tab:
"/json/:searchType/:userId?userId=123&searchType=user"
I have tried to create a fiddle, but because I use different views and json data from a server, I'm not sure how to create something that works in the fiddle that still looks anything like what I am actually doing. I have looked at this answer, this answer, this answer, this answer, and this answer, to name a few. I'm having trouble finding a posting that isn't about $resource, or the # notation it uses to link url params to object params though.
To explain, I'm using a service to pass the searchType and accountId params between controllers and my factory, which actually performs the $http request.
Here is my controller:
.controller('UserDetailsCtrl', ["$scope", "Search", function ($scope, Search) {
$scope.result = Search.getUser();
}])
Here is my Factory:
.factory('Search', ["$http", "SearchCriteriaSvc", function($http, SearchCriteriaSvc) {
var baseUrl = "/json/:searchType/:accountId";
return {
getUser: function () {
return $http.get(baseUrl,
{params:
{
accountId: SearchCriteriaSvc.getAccountId(),
searchType: SearchCriteriaSvc.getSearchType()
}
})
.then(function(result) {
return result.data;
});
}
}
}])
Finally, my service:
.service('SearchCriteriaSvc', function() {
var searchType = "",
userId = "";
return {
getSearchType: function () {
return searchType;
},
setSearchType: function(value) {
searchType = value;
},
getUserId: function () {
return userId;
},
setUserId: function(value) {
userId= value;
}
};
})
I have tried not using the service to pass the params (just manually typing in strings) and I get the same result, so I don't think that my service is the issue, but then, I'm at a loss.
Any help would be great. Thanks!
$http doesn't work in that way. You will have to collate your parameters into a URL yourself.
You are probably thinking of $resource which allows you to predefine HTTP request URLs and their parametric components, or $route which also allows similar parametric URL functionality but within the context of your angular front end rather than back end.
Any params passed in the way you pass them will end up as query / GET style parameters.
http://docs.angularjs.org/api/ng.$http
params – {Object.<string|Object>} – Map of strings or objects which will be turned to ?key1=value1&key2=value2 after the url. If the value is not a string, it will be JSONified.

Resources