This is the deal.
I what to show a spinner when doing a $http call, but the problem here is that I have multiple calls at ones, so the examples I found here didn't help.
Did anyone have a solution for this?
A way to stack the calls so the spinner remains until the last call finish? I hope to make my point.
Im doing this.
angular.module('moduleName', []).
factory.("SomeService", function () {
return:{
getResources(params) {
/* do the $http call */
}
}
}).
controller("SomeCtrl", function (SomeService) {
SomeService.getResources(params)
}).
controller("OtherCtrl", function (SomeService) {
SomeService.getResources(params)
});
The 2 controllers may call the service at the same time and the may get diferent responce.
All $http calls in Angular return a promise.
The $q service doesn't have all the bells and whistles of the Q library, which it is based, but if you look at the docs, it does have an all method that can be used to give you the functionality you want.
Here's how you could use it:
app.controller('HttpController', function($http, $q) {
// A hypothetical submit function
$scope.submit = function() {
// Set a loading variable for use in the view (to show the spinner)
$scope.loading = true;
var call1 = $http.get(/* ... */);
var call2 = $http.get(/* ... */);
var call3 = $http.get(/* ... */);
$q.all([call1, call2, call3]).then(function(responses) {
// responses will be an array of values the individual
// promises were resolved to. For this case, we don't
// need it, since we only care that they all resolved
// successfully.
$scope.loading = false;
}, function(errorValue) {
// If any of the promises is rejected, the error callback
// will be resolved with that rejection value, kind of like
// an early exit. We want to mark the loading variable
// as false here too, and do something with the error.
$scope.loading = false;
});
};
});
Use a variable that is initialized to the number of calls you are making. In each of the callbacks for the AJAX calls, call a function that decrements the value of this variable and if it's zero, removes the spinner.
num_calls = 3;
function considerRemoveSpinner() {
if (--window.num_calls == 0)
{
removeSpinner();
}
}
$http.get("url").success(
function() {
/* regular handler */
considerRemoveSpinner();
}
);
/* other ajax calls */
You can do this by using interceptor and passing one flag in the header of each API.What all you need to do it.Add one parameter in each $http call.
Than capture it in the interceptor Request method by using property config.headers.{{Parameter}}.On the basis of this flag broadcast one event for showing wait image.
Related
I have a service making two consecutive calls to an API asynchronously.
I would like the app to wait for both to be resolved before proceeding and since one of calls may or may not be made, I believe $watch is the way to go versus nested or chained callbacks.
var response_complete = {call1:false, call2:false};
$http.post("myapi.com/slug", data, header).then(function(res){
/* ... */
response_complete.call1 = true;
});
if(make_this_call==true){
$http.post("myapi.com/anotherslug", data, header).then(function(res){
/*...*/
response_complete.call2 = true;
});
} else response_complete.call2 = true;
$scope.$watch("response_complete",function(){
if(response_complete.call1==true && response_complete.call2==true){
console.log("DONE!");
}
});
So the idea is to create a global variable, and watch it as the two calls complete. The second call, which is conditional, immediately sets it's response variable to true if it is not being made.
But the $watch callback is only fired once and the condition within it (call1 & call2 == true) is never met.
your watch do not work as response complete is not a $scope variable | property:
// replace this with $scope property declaration
//var response_complete = {call1:false, call2:false};
$scope.response_complete = {call1:false, call2:false};
then in your succeeding code use $scope.response_complete to modify its value and so your $watch will be triggered as $scope.response_complete changed.
A better solution:
As others have specified it is better to use $broadcast than $watch, so instead watching the variable throw events instead and catch those event inside your $scope.
$http.post("myapi.com/slug", data, header).then(function() {
// stuff
$scope.$broadcast("POST_SLUG_COMPLETE");
});
$http.post("myapi.com/anotherslug", data, header).then(function() {
// stuff
$scope.$broadcast("POST_ANOTHERSLUG_COMPLETE");
});
// then in your $scope
$scope.$on("POST_SLUG_COMPLETE", function () {
// stuff
});
$scope.$on("POST_ANOTHERSLUG_COMPLETE", function () {
// stuff
});
hope that helps
If you need your "global" variable for the current scope, you can just do:
$scope.complete = false;
$http.post("myapi.com/slug", data, header).then(function(res) {
$http.post("myapi.com/anotherslug", data, header).then(function(res) {
$scope.complete = true;
console.log("DONE!");
});
});
You may also use $rootScope for a more "global" value. Other alternatives are $broadcast or a property inside a service.
But more important is to ensure how are you using the async calls. If you want both to be resolved put the second call inside the first. The sample provided by you wouldn't work because response_complete.call1 = true is inside an async thread and it is always false by the time you try to verify it
I'm having some problems understading how the callbacks work.
I'm writing a function that has to validate the user's input.
Inside the function I have to make an HTTP GET call to my API to perform a check based on the user input.
The problem is that the validate function is called from the process function and submit function is called before the HTTP call that I make inside validate().
I cannot edit process function because it is a function used by other components.
form.process = function(){
// do stuffs
validate();
submit();
}
form.validate = function () {
// lots of checks regarding the model
...
// HTTP GET call
}
Is it possible to let the submit function waits until the HTTP GET call inside validate() ends?
Thanks in advance :)
You MUST modify validate to return a promise like this:
form.validate = function () {
var deferred = $q.defer();
// lots of checks regarding the model
...
// In http GET call:
// If success
deferred.resolve(<any value>);
// If errors
deferred.reject(<any value>);
// and now return the promise
return deferred.promise;
}
Now you CAN do anything you want in process function like this:
form.process = function(){
// do stuffs
validate().then(function(response){
submit();
}, function(reject){
// do something like showing error.
});
}
If you have more components that use this function, you MUST edit all like this.
Anyway, this is the best way to implement other GET calls in each "validate" function of your components.
I have Item model with the methods like delete, rename etc. These are async methods, so while one's being executed I'm showing spinner on my views. Since there are many async methods on the Item model, I'm forced to always do like this in my controller:
function delete() {
isRequesting = true;
item.delete().then(function() {
isRequesting = false;
}
}
function rename() {
isRequesting = true;
item.rename().then(function() {
isRequesting = false;
}
}
These requesting= statements clutter my code and also there's a possibility to forget to specify it.
I also have a singleton fileNavigator with many async methods, so here I used events to control requesting:
fileNavigator.on(FileNavigatorEvents.REQUESTING, function (event, requesting) {
isRequesting = requesting;
});
And I no longer have to think about isRequesting when calling fileNavigators async methods.
My question is whether there's any similar pattern I can use for my Item non-singleton instances?
Are you using $resource? I would suggest Restangular and if you are using Restangular they have Request and Response Interceptors to fire off the broadcasts automatically to show and hide your Spinner.
Check out this: restangular: is it possible to have a progress bar ?
I have to deal with an non angular library and need to create a comunication between them.
<div id="MoipWidget" data-token="{{personaltoken}}" callback-method-success="successCB" callback-method-error="errorCB"></div>
Every time that the page is loaded, I have to get a token from my server.
$http.post('https://example.org', obj)
.success(function(data){
$scope.personaltoken = data.token;
//Here I call the non angular library and I get and error telling me that the token is undefined.
//If I run the code from inside a $timeout works as I need...
})
.error(function(data){
alert('error');
});
I've also tried to run inside $scope.$apply but I get an error telling that $digest already in progress
The non angularjs library that I have to call is simple is just two lines.
var settings = {}
LibraryCall(settings);
How can I update the model immediately?
I've tried to use $scope.$evalAsync as #Kjell suggested but did not work.
After reading more about $scope, I've found what I needed.
$scope.$applyAsync(function(){
var settings = {}
LibraryCall(settings);
});
$scope.$applyAsync will schedule the invocation of $apply to occur at a later time.
https://docs.angularjs.org/api/ng/type/$rootScope.Scope
I removed the error callback for brevity, don't do it in your code :)
I suppose the code you call is asynchronous, if it's not, you should not have any $scope updating problem (because all angular promises call $apply already)...
This should work:
$http.post('https://example.org', obj).success(function(data){
$scope.personaltoken = data.token;
otherLibrary.doSomething(data.token, function(error, result) {
$scope.changeSomething = 'toHey';
$scope.$apply();
});
});
This shoud also work:
$http.post('https://example.org', obj).success(function(data){
$scope.personaltoken = data.token;
otherLibrary.doSomething(data.token, function(error, result) {
$scope.$apply(function() {
$scope.changeSomething = 'toHey';
});
});
})
This shoud raise the $digest already in progress error, because $http does wrap the promise callback on a $apply call already.
$http.post('https://example.org', obj).success(function(data){
$scope.personaltoken = data.token;
$scope.$apply(function() {
otherLibrary.doSomething(data.token, function(error, result) {
$scope.changeSomething = 'toHey';
});
});
})
Try using either $scope.$evalAsync() or $scope.$applyAsync().
They are made for stuff like this. It will execute the code later in time. Not that different from $timeout, but potentially faster.
$scope.$evalAsync(function(){
var settings = {}
LibraryCall(settings);
})
Edit: Just to quote Ben Nadel on the difference between $timeout and $evalAsync, from this post:
So, in essence, $scope.$evalAsync() combines the best of both worlds:
When it can (which is most of the time), it will evaluate your
expression in the same tick; otherwise, it will evaluate your
expression in a later tick, which is exactly what $timeout() is doing.
So, the important bits:
When I bind a $scope variable in my controller directly to a $resource, angular's automatic promise resolution takes over and my view gets updated as soon as data comes back from the service. However, I need to poll the server for updates on an interval. Attempting to bind to the promise returned from the $interval call is not triggering angular's automatic promise resolution and I do not know if there is something that can be done to get this $interval working against a $resource.
Angular client, ASP.NET WebApi serving JSON, Angular $resource sitting over WebApi. Inside my angular controller, if I bind a $scoped variable to my $resource promises directly, I get results; however, I need my $resources to update on an $interval and I am having problems introducing the interval.
The working, "needs manual refreshing" code (the relevant part, at least)
'use strict';
var ctrls = angular.module('MyModule',[]);
ctrls.controller('FooCtrl', ['$scope', '$location','service',
function( $scope,$location,service)
{
$scope.Bars = service.Bars;
$scope.Baz = service.Baz;
}
var app = angular.module('MyModule.Services',['ngResource']);
app.service('service', ['$resource', function($resource)
{
var proxy = $resource(
'http://server/subsystem/api/Foo/:component',
{},
{
Bars: {method:'GET',isArray:false,url: 'http://server/subsystem/api/Foo/Bars'
,Baz: {method:'GET',isArray:false,rul: 'http://server/subsystem/api/Foo/Baz'
}
return proxy;
}]);
blah blah blah, ng-repeat in my view bound to "bar in Bars" = data to screen.
So now, I go into my controller to implement my interval, and I can get the interval working but the promise does not resolve.
The changes inside the controller (note that I added $interval and $document to my dependency list:
var stop;
$scope.Bars = {};
$scope.Baz = service.Baz; //Note that angular resolves this promise automatically.
$scope.pollService = function(){
if(angular.isDefined(stop))return;
stop = $interval(function(){
if(service){
$scope.Bars = service.Bars;
}
},1000);
};
$scope.stopPolling = function(){
if(angular.isDefined(stop)){
$interval.cancel(stop);
stop = undefined;
}
};
$scope.$on('$destroy',function(){
$scope.stopPolling();
});
$document.ready(function(){
$scope.pollService(); //when I attempt to execute my .then() on pollService(), I get "cannot
//call then on undefined"
});
Edit
Note that the problem I am having is that binding $scope.Bars directly to service.Bars triggers angular's automatic promise resolution but introducing the $interval call does not seem to be. Inspecting $scope.Bars in Chrome Dev Tools indicates that it is an unresolved promise.
I updated my sample code to match what I currently have in my debugger. I have several properties defined on my resource, and I included one that I named 'Baz'. If I assign $scope.Baz = service.Baz directly within my controller, the view binds to it when angular resolves the promise but the one on the interval never does.
You aren't returning the promise from $scope.pollService. Just add return stop; at the end of your function. Also note that the way your API is written, your function will still return undefined if stop is defined.
Therefore, it should probably look like:
$scope.pollService = function(){
if(!angular.isDefined(stop)) {
stop = $interval(function() {
if(service){
$scope.Bars = service.Bars;
}
}, 1000);
}
return stop;
};
stop is also not a very good descriptive name...
EDIT:
From my understanding after reading the docs, you should be invoking the action method on the service like $scope.Bars = service.Bars();. This will return an object that will eventually get populated with data once the promise is resolved. However, I think you also need to be careful here, because if the interval fires faster than the promise resolving rate you might run into issues. I think that you would be better off using $timeout.