AngularJS Service returning data instead of promise - javascript

I have a question conserning the use of services or factories inside an AngularJS application.
I Want to write a service that will return data from an Api which i can use inside my controllers.
Lets say i want to have a call like $scope.people = myPeopleService.getPeople();
and inside my service want to check if i already have people and return those otherwise i want to do a $http.get() and fill the people inside my service and then return them. I do not want to have .then inside my controller. Is this even possible?

If you dont want to return the promise to the consumer of your service:
.service('...', function ($http) {
var cachedResponse;
this.getStuff = function () {
if (cachedResponse) {
return cachedResponse;
}
return $http.get('someUrl')
.then(function (response) {
cachedResponse = response;
return response; // Return the response of your $http call.
});
};
});

My solution is to return a reference to an object that might be empty at first, but will hold the data eventually:
app.factory('myPeopleService', function($http){
var people = {};
$http
.post( "/api/PeopleService/getInitData", {
}).success(function (data, status, headers, config) {
angular.extend(people, data);
}).error(function (data, status, headers, config) {
});
return {
getPeople: function () {
return people;
}
};
});
the key is to use angular.extend in order to preserve the object reference.
in the controller you can use ng-show until the data is fulfilled, and/or use watch for processing

You won't be able to escape that at some level, somewhere, this call is asynchronous. One part of your question is easy, caching data is just an option in the $http call:
$http.get(url, {cache: true})
If you expose this call from a service only one call will be made over the network, no changes are required in your controller code.
Another thing to look into is using your router's resolve feature (ui-router and the vanilla router both support resolve). This can clean up some of the code in your controller.

It is not possible to do it without a .then in your use case. Since you want to make use of $http to fetch the people and return them (if they were not present already). $http works asynchronously and would return a promise. Due to the asynchronous behavior of $http it becomes mandatory to handle the promise returned in the calling function.
Hope this helps!

Related

Resolving a promise in Angular Service

We have a service, lets call it AccountService which exposes a method called getAccounts(customerId) among others.
In its implementation all it does is to fire up a $http GET request and return a promise to the calling controller which will put the returned array of accounts in the controller scope once resolved.
On a simplified view all looks like below:
// The service
.factory('AccountService', ['$http', function($http) {
var _getAccounts = function(customerId) {
var request = {
'method': 'GET',
'url': 'http://localhost:8081/accounts/' + customerId
};
return $(request);
};
return {
getAccounts: _getAccounts
};
}]);
// Inside the conntroller
AccountService.getAccounts($scope.customerId)
.then(function(response) {
$scope.accounts = response.data;
});
So once the promise will resolve the controller scope will get populated with the list of accounts.
Note that I kept the above code as simple as I could to get you the idea of what my problem is but in reality it will be code to deal with exceptions, watcher to refresh, etc. Everything works fine.
My problem is that this AccountService is used from lots of controllers and putting the promise resolve in all of these looks to me not only repeating all this boiler plate resolver code but also complicating the unit testing as I am obliged to r/test both successful and exception scenarios in every single controller test.
So my question is:
Is there a nice way to resolve the promise in the service and return the response to the controller, not the promise?
Please note I am a very beginner with Angular and JS so please be gentle if my question looks naive. I have heaps of java experience and my mind seems to go java like everywhere which may not be the case.
Thank you in advance for your inputs
To answer your original question:
Is there a nice way to resolve the promise in the service and return the response to the controller, not the promise?
In my opinion, no, there isn't. It boils down to the way asynchronous calls work - you either pass a callback (and the method returns nothing), or you don't pass a callback and the method returns an object which will be notified (a promise). There may be some workarounds, but I don't think it gets nicer than that.
One way to partially reduce the boilerplate is to use a catch in the service, and return the promise returned by it instead.
Consider the following extremely simplified example:
angular.module('myApp')
.factory('NetworkRequests', [
function() {
var _getData = function() {
var promise = new Promise((resolve, reject) => {
var a = true,
data = ['a', 'b', 'c'];
if (a) {
resolve(data);
} else {
reject('Rejection reason: ...');
}
});
return promise.catch((error) => {
// Notify some error handling service etc.
console.log(error);
return [];
});
};
return {
getData: _getData
};
}
]);
The promise variable would be the result from your http request. You should return some data in the catch function that makes sense in the controller context (e.g. empty array). Then you don't have to bother with error handling in the controller:
angular.module('myApp')
.controller('DataController', ['NetworkRequests',
function(NetworkRequests) {
NetworkRequests.getData().then((data) => {
this.data = data;
});
}
]);
Again, this doesn't solve the complete issue, but at least the error handling part can be encapsulated in the service.
You can design in such a way that once your $http is done with fetching the data, store it your factory variable (somewhat a cache), and for subsequent factory calls, you check if the cache has such data. If yes, return the cache data, else call the $http calls.
Here is the code:
.factory('AccountService', ['$http', '$q', function($http, $q) {
var cachedData = null;
var defered = $q.defer(); //create our own defered object
var _getAccounts = function(customerId) {
if (cachedData !== null) {
console.log('get from cachedData')
defered.resolve(cachedData); // resolve it so that the data is passed outside
return defered.promise; //return your own promise if cached data is found
} else {
var request = {
'method': 'GET',
'url': 'mockdata.json'
};
return $http(request).then((response) => { //return a normal $http promise if it is not.
console.log('get from $http');
cachedData = response.data;
return cachedData;
});
}
};
return {
getAccounts: _getAccounts
};
}]);
Here is the working plnkr. You can open up the console, and click the GetData button. You will see that first time it logs get from $http, where as subsequent calls it logs get from cachedData.
One way is to reuse an object and fill it with data. It is used by ngResource.
It is something like
var data = [];
function getAccounts(customerId) {
var promise = $http(...).then((response) => {
Object.assign(promise.data, response.data)
});
promise.data = [];
return promise;
};
Data is available for binding as $scope.accounts = AccountService.getAccounts(...).data. The obvious drawback is that there is a splash of unloaded content.
Another way is the one you've mentioned. It is being used most frequently. If there is a problem with WET code in controllers, it should be treated by eliminating WET code with class inheritance, not by changing the way it works.
Yet another way is the recommended one. Using a router and route/state resolvers eliminates the need for asynchronously loaded data. The data resolved in resolver is injected into route template as an array.

how to call a factory from a regular function in AngularJs

I have this factory which is called by some controllers.
app.factory('fileData', function($http) {
return {
get: function(filename){ return $http({ method: 'GET', url: filename});
}
};
});
Now I want to call it from a regular function and return the data from the factory. How can I do that? This one does not work because the fileData passed in is not recognized without $scope.
function getData (file, fileData) {
rels = [];
var handleSuccess = function(data, status) {
rels = data;
console.log(rels);
};
fileData.get(filename).success(handleSuccess);
return rels;
}
Any idea how to go around this?
Move return rels inside handleSuccess, you're returning before handleSuccess is getting called. So you're signaling that the function completed execution prematurely.
function getData (file, fileData) {
rels = [];
var handleSuccess = function(data, status) {
rels = data;
console.log(rels);
return rels; // Return rels when the Promise is finished
};
fileData.get(file).success(handleSuccess);
}
Since you're returning the promise from $http, you need to handle its execution in an asynchronous way which, is simply not returning until the promise is resolved.
It would also be a good idea to add a reject handler.
Additionally, .success() is deprecated and you should use .then() and .catch() respectively for handling resolve() and reject() of the Promise appropriately.
See below for the preferred approach to Promises with $http
fileData.get(file)
.then(handleSuccess)
.catch(handleFailure); // Handle any errors returned from $http
EDIT Update for DI example for controller
You need to inject the fileData factory into your Angular Controller. For more info on Dependency Inject see the Angular Docs for DI. Just to note, DI is a huge piece of Angular so it is pretty fundamental to understand this before proceeding.
angular
.module('yourApp')
.controller('yourController', ['$scope', 'fileData', function($scope, fileData) {
// Expose getData via $scope
$scope.getData = getData;
function getData(file, fileData) {
var rels = [];
var handleSuccess = function(data, status) {
rels = data;
console.log(rels);
return rels;
};
fileData.get(file)
.then(handleSuccess);
}
}]);
Your call to the method exposed in the factory (get) uses the $http service which returns a promise. So you cannot get the result of this call in a synchronous way.
In your sample the return rels happens way before the handleSuccess method is called and thus you are returning the old value (an empty array).
The reason it works inside of a controller function is that the controller instance is living for a longer time and when the server call returns successfully, the variable in you controller's $scope is assigned (which then is reflected in your UI via the angular data binding).
So the best way to solve this, is to avoid having a synchronous api in your "regular function". Could you probably make this also async by returning a promise?

Reading properties file value in Angular

I have gone through a few replies about using $http service for accessing the properties file, but now sure how it would fit in this scenario
I have created a service that returns the hostnames from the poperties file, the calling client to this service should make a blocking call to the service and proceed only if the property file is read.
var serviceMod = angular.module('serviceModule',[])
.factory('configService', function($http){
return {
getValue: function(key){
$http.get("js/resources/urls.properties").success(function(response){
console.log('how to send this response to clients sync??? ' + response)
})
return ????
}
}
})
someOtherControllr.js
var urlValue = configService.getValue('url')
The problem I am facing is to do with the aync nature of the $http service. By the time the response is received by the callback, the main thread is already finished executing the someOtherController.js
You need to resolve the promise returned by the service. We can just return the $http call and resolve it in our controller (since return $http.get be a promise itself). Check out the AngularJS $q and $http docs for a bettering understanding of the underlying mechanics going on, and observe the following change...
.factory('configService', function($http) {
return {
getValue: function(key) {
return $http.get('js/resources/urls.properties');
}
}
});
var urlValue;
// --asynchronous
configService.getValue('url').then(function(response) {
urlValue = response.data; // -- success logic
});
console.log('be mindful - I will execute before you get a response');
[...]
Simple way - use callback (it will still be async. In fact you cant make it sync) :
getValue: function(key, onSuccess){
$http.get("js/resources/urls.properties").success(function(response){
onSuccess(response);
})

Inside my own Angular service, how do you get the return value back to the Controller that called the service?

I'm having trouble figuring out the success and error callbacks inside $http in angular and the "then" part of a promise. The $http service returns a promise. Is the "success" callback an example of a "then"?
What if I stick $http inside my own service? How would I do this?
I think code is easier to show my questions:
app.controller('MainCtrl', function ($scope, $log, BooksService) {
BooksService.retrieveAllBooks().then(function(results) {
$scope.allBooks = results;
});
});
app.factory('BooksService', function($http,$log) {
var service = {
retrieveAllBooks: function() {
return $http({
method: 'GET','https://hugebookstore.org/allbooks'
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
}).success(function(data, status, headers, config) {
var allbooks = transformDataIntoBooksArray(data);//this function exists and just takes raw data and turns them into Book objects
return allbooks;/*how is this "success" callback related to the "then"() callback in app.controller above?*/
};
}).error(function(data, status, headers, config) {
$log.error('here is an error'+data + status + headers + config);
});
}
};
return service;
});
Questions about the app.controller code:
do I need to use a "then()" function inside app.controller?
I just want my controller to fetch me a list of all the books using the BooksService. The BooksService already has a "then" type of clause, which is the "success" callback. It seems like I have an unnecssary "then". Is that true?
Also, when is the "then" called, and can I put anything in the then's callback function's parameters?
I don't understand what parameters are supplied by default and what I can add.
Questions about the app.factory code:
First, this is what I understand the code to be doing.
I'm creating a BooksService using the factory() methods and not the service() methods available in Angular.
I'm defining a function called retrieveAllBooks() that under the hood, is making an asynchronous GET request using $http in Angular, which itself is a service. RetrieveAllBooks() is a nothing more than a wrapper around the $http GET service, which makes it easy for clients to simply call BooksService.retrieveAllBooks() and be handed an array of Book objects.
When is the "success" callback executed and what is its relation to the "then" callback in my app.controller?
It doesn't appear that a return value inside my success() callback is ever returned back to app.controller. Why?
How can I get the allbooks array returned back to the app.controller which is what originally called BooksService.retrieveAllBooks()?
Are there any other parameters allowed in the success callback?
What do I need to return from the retrieveAllBooks() function definition? Do I return the $http promise alone, or do I need to return something from both the success and error callbacks as well?
I know this is longwinded but I hope that someone can help me, b/c this to pattern--separating out my network calls into its own service layer as opposed to calling $http directly inside my controller layer, will be done over and over again.
Thanks!!
The success and error handlers are specific to the promise returned by $http. However, they're similar to .then(function onFulfill(){}, function onReject(){}).
It's up to you if you want to return that promise directly or implement something else.
Generally, your controller shouldn't be concerned about how your service handled the request. Thus, in my opinion, you shouldn't return that promise directly. Just handle the success and error case in your service. Return the result if the request succeeds and throw an Error if the request fails.
You can then use the generic .then(function onFulfill(){}, function onReject(){}) pattern in your controller, as this is always valid and expected with promises in Angular.
The onFulfill callback always receives your result as a parameter, the onReject callback receives the Error as a parameter.
So, what options do you have? Either drop your .success and .error callbacks in your service altogether and handle the transformation in the success case in your controller, or create a new deferred promise to return the transformed results:
app.factory('BooksService', function($http,$log,$q) {
var service = {
retrieveAllBooks: function() {
var deferred = $q.defer();
$http({
method: 'GET','https://hugebookstore.org/allbooks'
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
}).success(function(data, status, headers, config) {
var allbooks = transformDataIntoBooksArray(data);//this function exists and just takes raw data and turns them into Book objects
deferred.resolve( allbooks );
};
}).error(function(data, status, headers, config) {
$log.error('here is an error'+data + status + headers + config);
deferred.reject( new Error( 'here is an error'+data + status + headers + config );
});
return deferred.promise;
}
};
return service;
});
Although, we can save that if we consider:
- Errors are tracked by Angular already
- The deferred is excess, and can be avoided
- Content-Type and Accept are generated by Angular and the serer anyway.
- We can save the anonymous wrapper for our action
So, we end up with:
app.factory('BooksService', function($http) {
return {
retrieveAllBooks: function() {
return $http.get('https://hugebookstore.org/allbooks').
then(transformDataIntoBooksArray); // Error tracked by Angular already
}
}
});

Fetch data using $http for use with a custom service

I'm trying to create a custom service which pulls the list of all defined statuses from the server for use in forms, so far I have:
SimpleTaskApp.factory('storyStatus', function($http) {
var data = null;
var promise = $http({
url: '/story-status/',
method: 'GET'
}).success(function (resp) {
data = resp;
});
return {list: data }};
});
I understand this won't work since $http is asynchronous, however I have no desire to run the request more than once. I'd like to treat it as a static variable in effect, so if ever storyStatus.list is called, it should check whether it is empty and then attempt to populate itself.
You should work with promises instead of against them.
You don't need to store the data because you can store the promise. The point of promises is that you don't know when they'll be done. You don't know when your data will be available.
So your service must return the promise. But that's okay:
SimpleTaskApp.factory('storyStatus', function($http) {
var promise;
function getData() {
if ( ! angular.isDefined( promise ) ) {
promise = $http({
url: '/story-status/',
method: 'GET'
});
}
return promise;
}
return { list: getData };
});
In this service, your $http call will only be called once, but will return a promise whenever you call SimpleTaskApp.list(). Your controller that calls it doesn't need to know if the data is there yet because it will react the same regardless, e.g.:
SimpleTaskApp.controller('MainCtrl', function($scope, storyStatus) {
storyStatus.list().then(function( data ) {
$scope.statuses = data.statuses;
});
});
If the data is already retrieved, the then will run basically immediately; if not, it will run when the data gets back. The controller doesn't need to know anything about the internal state of the storyStatus service.
So promises not only help with asynchronous tasks, but they also help keep our components decoupled, which is a big deal in AngularJS - and should be in other frameworks too!
You could do this maybe?
SimpleTaskApp.factory('storyStatus', function($http) {
var promise = $http({
url: '/story-status/',
method: 'GET'
});
return { list: promise };
});
Calling code
storyStatus.list.then(function (data) {
});
You might want to consider using a synchronous ajax call to simplify your API if you are only loading the list once.

Categories

Resources