Angular Service/Controller not returning promise? - javascript

so I finally got my app working to where it gets the right URL for the JSON request. However now I can't get it work with that URL.
I understand that the service is returning the promise from the Google Maps API, which I probably shouldn't do but if I leave it out I get a "Weather.getWeather is undefined" error. I don't know why.
How can I get this to work correctly. Thanks for any help.
weatherService.getWeather = function(city) {
var coordsUrl = 'https://maps.googleapis.com/maps/api/geocode/json?address=' + city;
return $http.get(coordsUrl)
.success(function(data) {
var coords = data.results[0].geometry.location.lat + ',' + data.results[0].geometry.location.lng;
return getWeatherData(coords);
});
function getWeatherData(coords) {
var deferred = $q.defer(),
apiKey = 'cbbdddc644184a1d20ffc4a0e439650d',
weatherUrl = 'https://api.forecast.io/forecast/' + apiKey + '/' + coords + '?callback=JSON_CALLBACK';
$http.jsonp(weatherUrl)
.success(function(data) {
deferred.resolve(data);
}).error(function(err) {
deferred.reject(err);
});
console.log(weatherUrl);
return deferred.promise;
}
};
Controller:
vm.fetchWeather = function(city) {
Weather.getWeather(city)
.then(function(data) {
console.log(data);
vm.place = data;
});
};

You shouldn't use .success in your getWeather service function that wouldn't allow you return any sort of data. Because callback function's are not capable of returning anything Read Here about callback & promise. So you should go for the promise recipe to dealing with asynchronous request, basically you can return data from the promise function to consumer function which calls that function. Actually it does call the consumer .then function, when ajax completed.
You need to simply use .then function in your getWeather function, then on resolve of that async call it will call the getWeatherData function which again will again returns a promise. So when it gets resolved it call .then function of getWeatherData when it returns data from it, at that time Weather.getWeather(city)'s .then function will get call. This whole thing is nothing but you implemented in promise chain. One function wait for other's, once the underlying promise gets resolved, it call its .then function.
Read here about Promise
Code
return $http.get(coordsUrl)
.then(function(resp) {
var data = resp.data
var coords = data.results[0].geometry.location.lat + ',' + data.results[0].geometry.location.lng;
return getWeatherData(coords);
});
Also there is not need of creating an extra promise inside getWeatherData function as you can utilize the promise of $http call there.
Code
function getWeatherData(coords) {
var apiKey = 'cbbdddc644184a1d20ffc4a0e439650d',
weatherUrl = 'https://api.forecast.io/forecast/' + apiKey + '/' + coords + '?callback=JSON_CALLBACK';
return $http.jsonp(weatherUrl)
.then(function(resp) {
var data = resp.data;
//you could play with data here before returning it.
return data;
},function(error) {
return error;
});
}
Edit by Roamer-1888
Alternatively, modify getWeatherData() to accept data and to calculate coords for itself. Then, the flow control statement will simplify to return $http.get(coordsUrl).then(getWeatherData);.
weatherService.getWeather = function(city) {
var coordsUrl = 'https://maps.googleapis.com/maps/api/geocode/json?address=' + city;
function getWeatherData(data) {
var apiKey = 'cbbdddc644184a1d20ffc4a0e439650d',
coords = data.results[0].geometry.location.lat + ',' + data.results[0].geometry.location.lng,
weatherUrl = 'https://api.forecast.io/forecast/' + apiKey + '/' + coords + '?callback=JSON_CALLBACK';
return $http.jsonp(weatherUrl);
}
return $http.get(coordsUrl).then(getWeatherData);
};

Related

Http Service Factory Returns Undefined to Controller in Angular

I have this service, that initialize brands and actualBrand variables.
.factory('brandsFactory', ['$http', 'ENV', function brandsFactory($http, ENV){
var actualBrand = {};
var brands = getBrands(); //Also set the new actualBrand by default
function getBrands(){
var URL;
console.log('MOCKS enable? ' + ENV.mocksEnable);
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
$http.get(URL).
success(function(data){
console.log('data is :' + data);
actualBrand = data[0];
console.log('Actual brand is ' + actualBrand);
return data;
}).
error(function(){
console.log('Error in BrandsController');
});
}
var setActualBrand = function(activeBrand) {
actualBrand = activeBrand;
};
var isSetAsActualBrand = function(checkBrand) {
return actualBrand === checkBrand;
};
return {
brands : brands,
actualBrand : actualBrand
};
And my controller looks like this:
/* BrandController to manage the html content of a single brand */
.controller('BrandCController', function(brandsFactory, $scope){
$scope.brands = brandsFactory.brands;
console.log('brands in controller is ' + $scope.brands + ' -- ' + brandsFactory.brands);
});
The problem in the controller is that it gets undefined in brandsFactory.brands because its loaded before the service.
Why can I do to solve this?
Im really new in angular and javascript, maybe Im doing lots of thing wrong.
Any help I would be grateful, thank you.
The reason why it is undefined is actually because of the asynchronous nature of $http requests. There is nothing (well, basically nothing) you can do to change this behaviour. You are going to have to resolve this using the most common way (and if you continue with Angular and most web development languages, this will become a long-standing practices) with promises.
What is a promise? There are many guidelines online for what it is. The most basic way to explain it to you is that a promise will not execute until the asynchronous operation returns.
https://docs.angularjs.org/api/ng/service/$q
http://andyshora.com/promises-angularjs-explained-as-cartoon.html
Right now, when you console.log the property, it may/may not have already been loaded (in fact, it probably hasn't) thus you get undefined.
Furthermore, I am not exactly sure about this, but you are using the factory provider, yet you don't return anything from the provider function, thus you are using it as if it were a service. I'm not 100% sure if Angular allows this, but certainly the best practice way is to return the instance of what you want to create from the factory.
Im really new in angular and javascript, maybe Im doing lots of thing wrong. Any help I would be grateful, thank you.
You are correct -- you are doing lot's of things wrong. Let's start with the getBrands function:
//Erroneously constructed function returns undefined
//
function getBrands(){
var URL;
console.log('MOCKS enable? ' + ENV.mocksEnable);
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
$http.get(URL).
success(function(data){
console.log('data is :' + data);
actualBrand = data[0];
console.log('Actual brand is ' + actualBrand);
//SUCCESS METHOD IGNORES RETURN STATEMENTS
return data;
}).
error(function(){
console.log('Error in BrandsController');
});
The return data statement is nested inside a function which is the argument of an $http .success method. It does not return data to the getBrands function. Since there is no return statement in the getBrands body, the getBrands function returns undefined.
In addition the .success method of the $http service ignores return values. Thus the $http service will return a promise that resolves to a response object instead of a data object. To return a promise that resolves to a data object, use the .then method.
//Correctly constructed function that returns a promise
//that resolves to a data object
//
function getBrands(){
//return the promise
return (
$http.get(URL)
//Use .then method
.then (function onFulfilled(response){
//data is property of response object
var data = response.data;
console.log('data is :' + data);
var actualBrand = data[0];
console.log('Actual brand is ' + actualBrand);
//return data to create data promise
return data;
})
)
}
With a return statement inside the onFulfilled function and a return statement in the body of the getBrands function, the getBrands function will return a promise that resolves fulfilled with data (or resolves rejected with a response object.)
In the controller, resolve the promise with the .then and .catch methods.
brandsFactory.getBrands().then(function onFulfilled(data){
$scope.brand = data;
$scope.actualBrand = data[0];
}).catch( function onRejected(errorResponse){
console.log('Error in brands factory');
console.log('status: ', errorResponse.status);
});
Deprecation Notice1
The $http legacy promise methods .success and .error have been deprecated. Use the standard .then method instead.
UPDATE
I use $q promise to solve it. I use the .then in the controller, but I couldn't make it work in the factory. If you want to check it. And thanks for the feedback.
//Classic `$q.defer` Anti-Pattern
//
var getBrands = function(){
var defered = $q.defer();
var promise = defered.promise;
$http.get(URL)
.success(function(data){
defered.resolve(data);
})
.error(function(err){
console.log('Error in BrandsController');
defered.reject(err);
});
return promise;
};//End getBrands
This is a classic $q.defer Anti-Pattern. One of the reasons that the .sucesss and .error methods were deprecated was that they encourage this anti-pattern as a result of the fact that those methods ignore return values.
The major problem with this anti-pattern is that it breaks the $http promise chain and when error conditions aren't handled properly the promise hangs. The other problem is that often programmers fail to create a new $q.defer on subsequent invocations of the function.
//Same function avoiding $q.defer
//
var getBrands = function(){
//return derived promise
return (
$http.get(URL)
.then(function onFulfilled(response){
var data = response.data;
//return data for chaining
return data;
}).catch(function onRejected(errorResponse){
console.log('Error in BrandsController');
console.log('Status: ', errorResponse.status;
//throw value to chain rejection
throw errorResponse;
})
)
};//End getBrands
This example shows how to log a rejection and throw the errorResponse object down the chain to be used by other rejection handlers.
For more information on this, see Angular execution order with $q.
Also, Why is angular $http success/error being deprecated?.
And, Is this a “Deferred Antipattern”?
I think the issue you are having is that your controller is trying to access a property that is being set by an $http.get call. $http.get returns a promise, which is an asynchronous call to the URL you are providing to get your brands. That means that the controller making the call will not wait for the brands to be loaded.
There are a few options for solving this issue, but you can simply return the promise from the service and resolve it in the controller.
function getBrands(){
var URL;
console.log('MOCKS enable? ' + ENV.mocksEnable);
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
return $http.get(URL);
});
Then in your controller you can put the success and error functions as so...
brandsFactory.brands().then(function(response){
$scope.brand = response.data;
$scope.actualBrand = response.data[0];
}, function(err){
console.log('Error in brands factory');
});
There are other options as well (such as lazy loading or putting the factory directly on your scope), but you can look into those if you'd like.
I solve it using $q promise.
.factory('brandsFactory', ['$http', '$q', 'ENV', function brandsFactory($http, $q, ENV){
var actualBrand = {};
var getBrands = function(){
var URL;
var defered = $q.defer();
var promise = defered.promise;
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
$http.get(URL)
.success(function(data){
actualBrand = data[0];
defered.resolve(data);
})
.error(function(err){
console.log('Error in BrandsController');
defered.reject(err);
});
return promise;
};//End getBrands
var setActualBrand = function(activeBrand) {
actualBrand = activeBrand;
};
var isSetAsActualBrand = function(checkBrand) {
return actualBrand === checkBrand;
};
return {
setActualBrand : setActualBrand,
getBrands : getBrands
};
}])//End Factory
And in my controller:
/* BrandController to manage the html content of a single brand */
.controller('BrandCController', function(brandsFactory, $scope){
$scope.brands = [];
$scope.actualBrand = {};
$scope.setActualBrand = function(brand){
brandsFactory.setActualBrand(brand);
$scope.actualBrand = brand;
};
brandsFactory.getBrands()
.then(function(data){
$scope.brands = data;
$scope.actualBrand = data[0];
})
.catch(function(errorResponse){
console.log('Error in brands factory, status : ', errorResponse.status);
});
});//End controller
If I can improve my answer let me know. I use it all the information I got from the previous answers. Thank you anyone who helps.
UPDATE
Removing the $q.defer Anti-Pattern in my factory.
.factory('brandsFactory', ['$http', '$q', 'ENV', function brandsFactory($http, $q, ENV){
var actualBrand = {};
var getBrands = function(){
var URL;
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
return (
$http.get(URL)
.then(function onFulfilled(response){
var data = response.data;
//return data for chaining
return data;
}).catch(function onRejected(errorResponse){
console.log('Error in BrandsController');
console.log('Status: ', errorResponse.status);
return errorResponse;
})
);
};//End getBrands
var setActualBrand = function(activeBrand) {
actualBrand = activeBrand;
};
return {
setActualBrand : setActualBrand,
getBrands : getBrands
};
}]);//End Factory

AngularJS : Why when using back button on the browser my promises return before resolve with previous value?

Hi here is my code when I run the factory this one gets resolved before with the old data from the page before. if I put break points I get a hit on the callback before the promise is resolved
$scope.formElementsData = response; get there before deferred.resolve(formElements);.
// when I call it after using the back button
GetInputControlItemsService.getInputControlItems($routeParams.project +','+ $routeParams.templateid ).then(function(response) {
$scope.formElementsData = response;
});
app.factory('GetInputControlItemsService', [
'$http', '$q',
function($http, $q) {
var apiCall, deferred, factory, _getInputControlItems;
factory = {};
deferred = $q.defer();
apiCall = 'api/GetProjectInputControlItems/?projectAndTempletaeId=';
_getInputControlItems = function(projectAndTempletaeId) {
$http.get(webBaseUrl + apiCall + projectAndTempletaeId).success(function(formElements) {
deferred.resolve(formElements);
}).error(function(err) {
deferred.reject(err);
});
return deferred.promise;
};
factory.getInputControlItems = _getInputControlItems;
return factory;
}
])
Y also tried
GetInputControlItemsService.getInputControlItems($routeParams.project +','+ $routeParams.templateid ).then(function(response) {
return response;
}).then(function(response){
$scope.formElementsData = response;
});
and still not working any idea how to use this along with the back button of the browser ?
Also I notice if I call a factory inside a function for a search feature, it does the same thing it runs inside the callback before the promise is deferred.
So I end up calling it without promises and now it works here is the code without the factory service.
$scope.goSearch = function() {
if ($scope.searchKey !== '') {
$http.get(webBaseUrl + apiCall + $scope.searchKey).success(function(results) {
$scope.projects = results;
});
}
};

angular.forEach Resolving Nested Promises

I have to make sequential AJAX calls after retrieving a collection of data. I am having issues resolving the nested promises.
Basically, I need to extend each object returned in my first collection with a property of ActionItems and set it's value with a promise then resolve each promise in the collection.
Any help would be greatly appreciated.
Factory
$http.get(urlBase + 'Project?$expand=Plant,CreatedBy,ModifiedBy,Plant/Contacts').then(function(success){
var contents = {};
contents = success.data.d.results;
return contents;
})
.then(function(contents){
var contentPromises = [];
angular.forEach(contents, function(content) {
contentPromises.push(
$http.get(urlBase + "ActionItems?$filter=ProjectId eq " + content.Id ).then(function(success){
content['ActionItems'] = success.data.d.results;
})
);
});
return $q.all(contentPromises).then(function() {
return contents;
});
});
Current Output is undefined
Well turns out this method works, but the key to getting your data back is returning it...
//Forgot the return below...
return $http.get(urlBase + 'Project?$expand=Plant,CreatedBy,ModifiedBy,Plant/Contacts').then(function(success){
var contents = {};
contents = success.data.d.results;
return contents;
})
.then(function(contents){
var contentPromises = [];
angular.forEach(contents, function(content) {
contentPromises.push(
$http.get(urlBase + "ActionItems?$filter=ProjectId eq " + content.Id ).then(function(success){
content['ActionItems'] = success.data.d.results;
})
);
});
return $q.all(contentPromises).then(function() {
return contents;
});
});
Thanks for all who helped.
Your issue lies within the $http.get(...).then() part.
The documentation for .then is telling us that "This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback".
So the promise returned by .then is different than the one returned by $http.get. And you are responsible of resolving or rejecting it (by returning)! The promise returned by .then is the one pushed to contentPromises.
Thus you need something like this:
angular.forEach(contents, function(content) {
contentPromises.push(
$http.get(urlBase + "ActionItems?$filter=ProjectId eq " + content.Id ).then(function(success){
content['ActionItems'] = success.data.d.results;
return success;
})
);
});
You'd do well to implement the errorCallback too.

Typescript sequence of a Class methods

I got a typescript class with an attribute that contain some connection data that are the result of an ajax call, here's a snippet:
class User {
// ... other stuff (constructor, attributes, methods)
static data:{id:number; token:string} = {id: 0, token: ""};
connect() {
// Ajax Call
.done(function(e){
User.data.id = e.id;
User.data.token = e.token;
})
}
request() {
if(User.data.id == 0)
setTimeout(() => {
this.request();
}, 500);
else return '?id=' + User.data.id + '&token=' + User.data.token;
}
}
I tried to use connect() and subsequently request() but sometimes the request() function starts before ajax's answer. Now I'm trying to write the request() function with some waiting time and a sort of recursion. Unfortunately it doesn't work.. My goal is to call the request() function and obtain the string only when "id" and "token" are ready (not 0 and empty string). Any suggestion would be appreciated!
PS: I can't put the request() function inside the ajax callback: the two functions should be separated
The calling code should be combining the promises with then. connect() and request() should just return their promises. e.g. they "promise to return done when connected" and "promise to return the requested data". In the case of request, it does not need a deferred object or promise as it simply returns a value immediately.
JSFiddle example: http://jsfiddle.net/TrueBlueAussie/qcjrLv4k/3/
// Return a fake connection after 5 seconds
var connect = function (){
var def = $.Deferred();
setTimeout(function(){
def.resolve();
}, 5000);
return def.promise();
}
var request = function(){
return "?data=d";
}
and use like this:
connect().then(request).done(function(data){
alert(data);
});
So in your case, just return the ajax call result as a promise from connect():
connect() {
return $.ajax({[snipped]})
.done(function(e){
User.data.id = e.id;
User.data.token = e.token;
});
}
and request simply returns the string:
request() {
return '?id=' + User.data.id + '&token=' + User.data.token;
}
Another way to do it is to save the connect promise:
var promise = connect();
and use it whenever you want to get the request data:
promise.then(request).done(function(data){
alert(data);
});
A better way, if your request is dependent on a "connection", is to pass the connect promise to the request method as a required parameter:
request(connectionPromise){
return connectionPromise.then(function(){
return '?id=' + User.data.id + '&token=' + User.data.token;
});
};
and call with:
request(connect()).done(function(data){
alert(data);
});
Example using this approach here: http://jsfiddle.net/TrueBlueAussie/qcjrLv4k/4/
Final example (based on commented desire to reuse connection). Combine the previous answers like this:
1) Save the connection as a property of the object.
// Start the connection process and save the promise for re-use
var connectionPromise = connect();
2) Use the connection as a parameter to the requests (so they do not need to have knowledge of the external variable):
// Make a request, passing the connection promise
request(connectionPromise).done(function(data){
alert(data);
});
3) The request method does not change from the previous example:
request(connectionPromise){
return connectionPromise.then(function(){
return '?id=' + User.data.id + '&token=' + User.data.token;
});
};
Once you are in promise land you need to stay there (or use a callback):
class User {
// ... other stuff (constructor, attributes, methods)
static data: { id: number; token: string } = { id: 0, token: "" };
private connectedPromise;
connect() {
// Ajax Call
this.connectedPromise = something.done( function ( e ) {
User.data.id = e.id;
User.data.token = e.token;
})
}
request() {
this.connectedPromise.then( () => {
if ( User.data.id == 0 )
setTimeout( () => {
this.request();
}, 500 );
else return '?id=' + User.data.id + '&token=' + User.data.token;
})
}
}
Store the result of connect in a var on only execute request if connect has resolved.
PS the request function is bad (I haven't cleaned it up but removed the return) in that it is maybe async i.e. it either returns a value or does some async work. Better to be consistent and always be async.

Asynchronous loading data issue AngularJS and Firebase

I am having difficulties loading data from Firebase into my AngularJS factory and thus controllers.
Basically I have the following factory:
.factory('usersList', ['fbutil', function(fbutil) {
return {
all: function() {
return fbutil.syncArray('users');
},
get: function(userId) {
fbutil.syncArray('users').then( function(result) {
window.alert(result.length) // FIRST ALERT
var x = 0;
for (var i = 0, len = result.length; i < len; i++) {
if (result[i].uid == userId) {
window.alert("fount id") // SECOND ALERT
x = i;
} else {
window.alert("nope"); // THIRD ALERT
}
}
return result[x];
}) // then
} // get
} // return
}]); // usersList
And my controller looks like:
.controller('OverviewCtrl', ['$scope', 'fbutil', 'usersList', function($scope, fbutil, usersList) {
$scope.usersList = usersList.all();
$scope.testUser = usersList.get("simplelogin:29");
// OTHER CODE
};
}])
In my HTML file, when I call {{usersList}} then it produces the result:
[{"color":"#CC0066","email":"a#a.com","name":"Eva","uid":"simplelogin:27"},{"color":"#009933","email":"b#b.com","name":"Semko","uid":"simplelogin:28"},{"color":"#CC0066","email":"c#c.com","name":"Caroline","uid":"simplelogin:29"}]
But testUser does not load, just shows {{tesUser}} in the index file.
Does anyone know how to handle this correctly? Without using the then(), which in this example also does not work, I figured out from the first alert that the result.length equaled 0, which gave me the suggestion that I am dealing with asynchronous loading. That is why I am trying to handle it whit .then() but apparently it is not working.
To handle a promise in angularjs the best way it to use defer values, since it allows you to process it before returning the data while keeping everything non blocking. With $http I would process like this :
function get(id) {
var deferred = $q.defer();
var url = "anUrlwithid";
$http.get(url).success(function(data, status) {
logger.logDebug('Successful GET call to ' + url + '\n, results ' + status + ': ' + data);
deferred.resolve(function(data){
//do something to your data then return
});
}).error(function(data, status) {
logger.logDebug('Failed GET call to ' + url + '\n, results ' + status + ': ' + data);
deferred.reject(data);
});
return deferred.promise;
}
And to process it in the controller :
get(1).then(function(data){//update scope or do something with the data processed});
You should be able to use that with your fbutil since it returns a promise I think.
Hope it helps
More details on the Q module here : https://docs.angularjs.org/api/ng/service/$q
PS: the logger is one of my personal service, just use console.log instead

Categories

Resources