How to handle $http asynchronous issues in angularjs factory service? - javascript

In the factory service js file, I created a service like this:
DashboardFactory.restService = function(method) {
var teststatus = "aaa";
switch (method) {
case "getAll":
$http.get($_REST_API_ROOT_URL + "clients").success(function(data) {
teststatus = data;
}).error(function(error) {
teststatus = 'Unable to load the client data: ' + error.message;
});
teststatus = "bbb";
break;
}
return teststatus;
};
In controller, the code is like this:
$scope.AllClients=DashboardFactory.restService("getAll","","");
I also put the "AllClients" on the html page to monitor the result:
{{AllClients}}
I think the "AllClients" should show the API data from the remote server. But in reality it always give me "bbb".
What should I do?
Thanks in advance!

teststatus gets set as "bbb" and is returned before the API call is finished since the call is asynchronous.
Refer to AngularJS $http call in a Service, return resolved data, not promises for the different ways you can return the data from your API call.
What you want to do is return teststatus at the end of your .success()/.error() functions rather than outside. That way, it only returns once it finishes the API call and sets it to the data returned by the call.

Method 1: You don't NEED to return it at all. You could send in your model to the service RestService.getAllClients = function(myModel), set a property on the model on success myModel.AllClients = data; and then display it with {{myModel.AllClients}}. Setting the property directly on the passed in model will update the binding automagically.
Example: http://jsbin.com/muxijozofa/edit?html,js,output
Method 2: Otherwise you'd need to return the entire get call, which will return a promise, which you'll then need to resolve on the controller as per f.e How do I return data from a $http.get() inside a factory in angularjs
Refactoring tip: Instead of building a "catch-all" rest service filled with switch-case:s, you could build a general rest-service factory which you then implement for each type of call. So you get one method per type of rest call. Instead of DashboardFactory.restService, you'd then call RestService.getAllClients, which sets the data or returns a promise per the methods above.
Switch-case is just bad for feature selection, you're hiding functionality and making the code prone to hidden errors.

Related

Why move my $http calls into a service?

Currently I have calls like this all over my three controllers:
$scope.getCurrentUser = function () {
$http.post("/Account/CurrentUser", {}, postOptions)
.then(function(data) {
var result = angular.fromJson(data.data);
if (result != null) {
$scope.currentUser = result.id;
}
},
function(data) {
alert("Browser failed to get current user.");
});
};
I see lots of advice to encapsulate the $http calls into an HttpService, or some such, but that it is much better practice to return the promise than return the data. Yet if I return the promise, all but one line in my controller $http call changes, and all the logic of dealing with the response remains in my controllers, e.g:
$scope.getCurrentUser = function() {
RestService.post("/Account/CurrentUser", {}, postOptions)
.then(function(data) {
var result = angular.fromJson(data.data);
if (result != null) {
$scope.currentUser = result.id;
}
},
function(data) {
alert("Browser failed to get current user.");
});
};
I could create a RestService for each server side controller, but that would only end up calling a core service and passing the URL anyway.
There are a few reasons why it is good practice in non-trivial applications.
Using a single generic service and passing in the url and parameters doesn't add so much value as you noticed. Instead you would have one method for each type of fetch that you need to do.
Some benefits of using services:
Re-usability. In a simple app, there might be one data fetch for each controller. But that can soon change. For example, you might have a product list page with getProducts, and a detail page with getProductDetail. But then you want to add a sale page, a category page, or show related products on the detail page. These might all use the original getProducts (with appropriate parameters).
Testing. You want to be able to test the controller, in isolation from an external data source. Baking the data fetch in to the controller doesn't make that easy. With a service, you just mock the service and you can test the controller with stable, known data.
Maintainability. You may decide that with simple services, it's a similar amount of code to just put it all in the controller, even if you're reusing it. What happens if the back-end path changes? Now you need to update it everywhere it's used. What happens if some extra logic is needed to process the data, or you need to get some supplementary data with another call? With a service, you make the change in one place. With it baked in to controllers, you have more work to do.
Code clarity. You want your methods to do clear, specific things. The controller is responsible for the logic around a specific part of the application. Adding in the mechanics of fetching data confuses that. With a simple example the only extra logic you need is to decode the json. That's not bad if your back-end returns exactly the data your controllers need in exactly the right format, but that may not be the case. By splitting the code out, each method can do one thing well. Let the service get data and pass it on to the controller in exactly the right format, then let the controller do it's thing.
A controller carries out presentation logic (it acts as a viewmodel in Angular Model-View-Whatever pattern). Services do business logic (model). It is battle-proven separation of concerns and inherent part of OOP good practices.
Thin controllers and fat services guarantee that app units stay reusable, testable and maintainable.
There's no benefit in replacing $http with RestService if they are the same thing. The proper separation of business and presentation logic is expected to be something like this
$scope.getCurrentUser = function() {
return UserService.getCurrent()
.then(function(user) {
$scope.currentUser = user.id;
})
.catch(function(err) {
alert("Browser failed to get current user.");
throw err;
});
});
It takes care of result conditioning and returns a promise. getCurrentUser passes a promise, so it could be chained if needed (by other controller method or test).
It would make sense to have your service look like this:
app.factory('AccountService', function($http) {
return {
getCurrentUser: function(param1, param2) {
var postOptions = {}; // build the postOptions based on params here
return $http.post("/Account/CurrentUser", {}, postOptions)
.then(function(response) {
// do some common processing here
});
}
};
});
Then calling this method would look this way:
$scope.getCurrentUser = function() {
AccountService.getCurrentUser(param1, param2)
.then(function(currentUser){
// do your stuff here
});
};
Which looks much nicer and lets you avoid the repetition of the backend service url and postOptions variable construction in multiple controllers.
Simple. Write every function as a service so that you can reuse it. As this is an asynchronous call use angular promise to send the data back to controller by wrapping it up within a promise.

Prevent AngularJS $http promise from returning cached JSON data

In my AngularJS app's signup process, I'm constantly updating a user object.
This may be the wrong way to do it, but I'm using one "signup" controller for all of these signup steps (as the logic in the steps is all very similar and it makes for neater code rather than making a separate "signup1", "signup2" etc controller for each view.
I have a promise that returns userInfo from my database:
.service('userInfo', function ($http, $cookies) {
var userId = $cookies.id;
console.log("UI Cookies", $cookies);
var promise = $http.get('/api/findProfile/' + userId, { cache: false}).
success(function (data) {
var userInfo = data;
console.log("This is fresh data!") // This logs the first time I load my page, but never again
return userInfo;
}).error(function(data) {
console.log("ERROR")
return userInfo
});
return promise;
})
When going from one signup step to the next, I update the profile info on MongoDB then load the next page. If I load the page via changing $location('') from my controller, I get old userInfo that hasn't been updated in my database. However, if I do a full page refresh I get the correct, updated userInfo.
Is this a caching issue? I've tried passing {cache: false} to my $http promise but I'm not getting fresh data (as denoted by the console.log("This is fresh data!"); rather, I'm getting cached data back.
How can I resolve this beyond forcing a full-page reload in Angular?
This is not a caching issue, or at least not the way you think.
Services and factories are singletons in angular, meaning that they get initialized once, before they are first requested to be injected. After that, the service function will not run, all controllers will receive the same object. (In case of a service, the injected object is created by using the service function as a constructor)
If you want to poll for some data every time a view is loaded, you have to initialize the request in a controller (you might do this via a service regardless) - which does get re-initialized without reloading the browser page.
Also, you seem to have some issues with using promises and return values. There's no point in returning from the error and success handlers, as their return value will be discarded. See this discussion for more information on "returning" from async functions.
It's probably easiest if you just return the $http promise from a function in the service, then every controller can handle the promise appropriately, ultimately reducing your service code to this:
.service('userInfo', function ($http, $cookies) {
var userId = $cookies.id;
console.log("UI Cookies", $cookies);
//this function returns the promise returned by $http.get
this.getData = function(){
return $http.get('/api/findProfile/' + userId, { cache: false});
};
})

Angular Best practice: promise in a Factory or in a Controller?

I have a basic factory in my app that handles API calls. Currently I'm using the form:
.factory('apiFactory', function($http){
var url = 'http://192.168.22.8:8001/api/v1/';
return {
getReports: function() {
return $http.get(url+'reports').then(function(result) {
return result;
});
},
getReport: function(id) {
return $http.get(url+'report/'+id).then(function(result) {
return result;
});
}
}
})
And in my controller I'm handling the promise like so:
.controller('exampleController', function($scope, apiFactory) {
apiFactory.getReports().then(
function(answer) {
if (answer.status==200){
if (answer.data.status == "error"){
// DISPLAY ERROR MESSAGE
console.log(answer.data.msg);
}
} else{
// THROW error
console.log('error: ', answer);
}
},
function(error){
console.log('error: ', answer);
}
);
}
}
})
It seems I could move the promise handling to my Factory instead of doing it in my controller, but I'm not sure if that would have any benefits others than a smaller controller.
Could somebody explain the best practices regarding this pattern?
It is ultimately up to you how much data you want to provide to the caller of the service. If needed, you could definitely return the HTTP response object to the caller, and have them process the response (which, btw, is always HTTP 2xx, if the promise is resolved rather than rejected).
But if you want to isolate the caller from the specifics of how the data got there (maybe it was cached, or supplied via another mechanism), and if you need to post-process the data, then it is advisable to handle the response in the service.
Here's an example:
.factory("apiService", function($http, $q){
var url = 'http://192.168.22.8:8001/api/v1/';
return {
getReports: function() {
return $http.get(url+'reports').then(function(result) {
var data = result.data;
if (data === "something I don't accept"){
return $q.reject("Invalid data");
}
var processedData = processData(data);
return processedData;
})
.catch(function(err){
// for example, "re-throw" to "hide" HTTP specifics
return $q.reject("Data not available");
})
},
// same idea for getReport
}
});
Then the controller wouldn't need to care about the underlying mechanism - all it gets is data or a rejection.
.controller('exampleController', function($scope, apiService) {
apiService.getReports()
.then(function(reports){
$scope.reports = reports; // actual reports data
});
})
Off-topic:
Notice how I changed the name of the service from "apiFactory" to "apiService". I wanted to point that out to remove a possible misconception. Whether you use .factory or .service or .value what you get as an injectable is always a service instance. .factory is just a mechanism of how this service is instantiated, so the name "apiFactory" is a misnomer. The only "factory" here is a function that you register with .factory (which could be anonymous, of course):
.factory("fooSvc", function fooSvcFactory(){
return {
getFoo: function(){...}
}
})
Better to keep all the data fetching inside the factory. This keeps the controller free from state, and it no longer cares how your factory works. If you change how you get data (e.g. not using $http) your controller shouldn't care, as it just calls getReport() and
A good explanation (see "Resolving Model data, no callback arg binding in Controllers"):
http://toddmotto.com/rethinking-angular-js-controllers/
Short answer: Handle promises in Factory.
Why?
Problems you face if you handle promises in Controller:
Let's say you have 5 Controllers that use the same Factory. Now let's say that you want to handle the errors when the promise does not get resolved correctly. So in the first controller, you write an error callback (or the catch(exception) more precisely, as you are dealing with promises), that shows you an alert message with the error. When the promise fails, this controller shows an alert with the error message. So far, so good? Right. But wait! What about the other 4 controllers? You haven't handled the errors in them. So now you end up copying the error handling code from the first controller & pasting it in the rest of the 4 controllers.
Now the fun starts. Imagine that you want to change your logic in the error state. Maybe you want to just log the error in console, or show a toaster message perhaps. So you go to the first controller & update the code. You think you are done? NO!!! There are 4 other controllers that are showing the alert message (remember that you pasted the code from the first controller previously???). So now, you try to update the rest of the controllers by pasting the new error message logic. Think about all the controllers that you need to update, if you have more!!! Tiring, isn't it???
Why to resolve promise in Factory:
Now let's say that you write your error handling logic in your service and all the controllers are using this service. You have written code to show an alert in case of a promise that fails in this service. All controllers will now start showing this alert, in case of a broken promise. In future, if you wish to update your error logic, you simply update this error logic in your service & all your controllers will start using this updated logic automatically, without any more effort on your part. This way, you have saved yourself tons of time.
So think about this:
1 change in Factory vs 1 change in all Controllers (which maybe 1 or more)
I would definitely vouch for 1 change in Factory as that helps reuse my logic with just a simple change and only once. All my controllers will start using the new logic. Controllers are supposed to be slim, lean & clean. Factories & Services are supposed to be reusable. For this reason of reusability, I strongly suggest you handle promise in Factory/Service.

Which things i can put in service in angular Js

I have read many articles but still could not find how to put stuff in services.
Currently this is my Service
angular
.module('users')
.factory('objectiveService', objectiveService);
objectiveService.$inject = ['$http', 'Restangular'];
function objectiveService($http, Restangular) {
return {
getObjectives: getObjectives,
getSingleObjective: getSingleObjective
};
function getObjectives(pid) {
var pr = Restangular
.all('api')
.all('users')
.one('subjects', pid)
.all('objectives');
return pr;
}
function getSingleObjective(oid) {
var pr = Restangular
.all('api')
.all('users')
.one('objectives', oid);
return pr
}
}
This is the controller:
var _vm = this;
this.objPromise = objectiveService.getObjectives(44);
function getData() {
var promise = _vm.objPromise;
promise
.getList(filters)
.then(function(result) {
$scope.gridData = result;
});
}
function remove(id) {
if (confirm('Are you sure you want to delete this!')) {
objectiveService.getSingleObjective(id).remove().then(function() {
$scope.getData();
});
}
}
// initial call
$scope.getData();
In this code i basically see no use to define service because I still has to use then() in the controller to assign data to Grid.
Also I can't use then() in service because there I don't have $scope to update the data.
People say to do all stuff in service but I am not able to figure out how.
Ideally I want to put all functions like remove(object_id) in service
IS it possible that I can just do objectiveService.remove(id) in controller.
But then I have to call $scope.getData() after deleting which I can't do in service
The reason to use services is not to avoid the use of .then. In your controller you would use .then like so:
objectiveService.getObjectives(filters)
.then(function(result) {
$scope.gridData = result;
});
.then is required as the call to get data occurs asynchronously.
Some reasons taken from John Papa's style guide:
The controller's responsibility is for the presentation and gathering of information for the view. It should not care how it gets the data, just that it knows who to ask for it.
This makes it easier to test (mock or real) the data calls when testing a controller that uses a data service.
Data service implementation may have very specific code to handle the data repository. This may include headers, how to talk to the data, or other services such as $http. Separating the logic into a data service encapsulates this logic in a single place hiding the implementation from the outside consumers (perhaps a controller), also making it easier to change the implementation.
If you put the code in the controller it also means it's not reusable. You can't use it in more than one controller or you can't you it in another service.

Using remote data in AngularJS app

Can someone ELI5 how to grab json data via GET in Angular, and have it available to a controller -
I'm currently doing this:
app.factory('dataRepo', function($http) {
return {
getData: function() {
return $http.get('/data.json');
}
};
});
app.controller('mainCtrl', function ($scope, $http, _, dataRepo) {
dataRepo.getData().success(function(d) {
$scope.data= d;
console.log($scope.data)
});
console.log($scope.data);
/* more stuff in here using $scope.data
});
The first console.log call logs the data, the second [outside the success function] logs undefined, and writes prior to the other call.
How do I get the data before the app kicks off, or how do I defer the remainder of the code until `$scope.data is populated?? I've looked at promises, and can get the result I need doing what I know is a really dodgy implementation and I don't want to persist with it
U can register a watcher on data and get notified about every change. If data is an array, the third parameter is a boolean watching objects more deeply.
$scope.$watch('data', function(newValue, oldValue) {
}, /*true*/);
Thats because the $http.get call is asynchronous, aka the script continues without calling the callback directly. Therefore the second call to console.log() is executed first and $scope.data is still undefined at this point. Later when the http request is completed the function block inside success() is executed and $scope.data becomes populated.
Although promises make handeling with asynchronous code much easier it doesn't make it synchronous like you'll generally code on the backend.
A solution could be to place the logic which requires the data inside the success() callback.
If you need the data outside the mainCtrl you can use the $watch method like #angabriel suggested, but this code needs to handle de data in all cases. 1: undefined, 2: populated.
3: changed again?
If you're using angular routes to create the controller (and not ng-controller="mainCtrl")
You can use the resolve property to delay the controller/view until the data is loaded shown here

Categories

Resources