How long does an angular promise live? - javascript

I'm trying to understand the life cycle of angular services and components.
Say I have a controller that uses an http service:
function MyController($scope, $http) {
$http.get('/service').success(function(data) {
// handle the response
});
}
The thing is that this controller may be attached to a view. And I want to make sure that the response is discarded if the view has been removed, this is to prevent conflicts with other requests thay may be triggered in other parts of the application. Will the instance of the controller be destroyed and with it, pending calls from the $http service be canceled if the view is removed? For example, when the user navigates away (without reloading) from the page causing a Javascript render of a new section?
[Edit] I created a jsfiddle that shows that, at least for the $timeout service, the pending operations are still running after the $scope is destroyed by navigating away. Is there a simple way to attach async operations to the scope so that they will be destroyed automatically?

First, attach a reference to your promise, then pass that reference to the cancel function. This resolves the promise with a rejections. So you could also just use promise.reject() in place of cancel(promise)
function MyController($scope, $http) {
var promise = $http.get('/service');
promise.success(function(data){
});
$scope.$on(
"$destroy",
function() {
promise.reject("scope destroyed, promise no longer available");
}
);
}

Related

Why would $q take a while to resolve

I'm writing an AngularJS plugin for Umbraco and have created a simple view, controller and service. But for some reason my promise is taking a while to resolve.
I have used the inbuilt $q service to create and return my promise, I have logged out my variables and can see when the async service finishes but there is a noticeable time difference between that and the resolve function being called.
I have since discovered the promise looks like it is waiting for Umbracos GetRemainingTimeout service before it resolves.
Can someone explain why this might be happening?
viewController.js
angular.module('umbraco')
.controller('JaywingAnalyticsHelper.ViewController', function ($scope, googleService) {
googleService.checkAuth().then(function (signedIn){
$scope.isAuthorised = signedIn;
console.log(signedIn);
});
});
googleService.js
angular.module("umbraco")
.service('googleService', function ($q) {
var clientId = 'REMOVED_FOR_PRIVACY',
scopes = ['https://www.googleapis.com/auth/analytics.readonly'],
deferred = $q.defer();
this.checkAuth = function () {
gapi.load('auth2', function () {
gapi.auth2.init().then(function () {
var googleAuth = gapi.auth2.getAuthInstance();
var signedIn = googleAuth.isSignedIn.get();
console.log(signedIn);
deferred.resolve(signedIn);
}, function(){
deferred.reject(false);
});
});
return deferred.promise;
};
});
Umbraco version - 7.5.12
Angular version - 1.1.5
After finding some time to revisit this issue I have discovered the cause of why the promise was taking so long to respond.
Most endpoints can be reached within angular by using the $http service but gapi uses its own methods to make the requests and due to the angular life-cycle it is important to call $apply which prompts angular to update any bindings or watchers.
Two links here to the documentation and another great resource:
https://code.angularjs.org/1.1.5/docs/api/ng.$rootScope.Scope#$apply
http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
This was annoyingly simple and can only be blamed on my lack of angular knowledge. In my case the promise was waiting for angular to get to a certain point in its life-cycle before resolving the promise rather than updating instantly. Wrapping it in the apply function fixes this problem.
$rootScope.$apply(function(){
deferred.resolve(signedIn);
});
For those interested there was a number of steps which led me to diagnosing this issue, including:
Moving the gapi call out of the service and back into the controller
This didn't have any effect, and the promise was still taking a while to resolve.
Swapping out the gapi call for a setTimeout
Again this didn't have any effect, and the promise was still taking a while to resolve but did show that the issue wasn't directly related to gapi.
Adding multiple setTimeouts of different lengths
This was the next step as it proved that the promises were resolving at the same time even though they should be seconds apart. Two important discoveries came out of this. Interacting with the view caused the promises to resolve (some kind of life-cycle trigger) and that there is an angular version of setTimeout called $timeout
Read into why $timeout exists
This led to learning more about the angular life-cycle and the $apply function why and when to use it. Problem solved.

Calling $http promise inside controller

Thanks in advance for the help...
I have a controller I'm using to call an API to send a password reset link. I have abstracted the actual $http call off into a service to keep the controller thin. Originally I was doing something like this:
angular.module('module')
.service('forgotPasswordService', ['$http', function($http) {
$http(request).then(function() {
return {}; //return some object using response
}]);
I felt like this would be the best approach as it would keep the controller as thin as possible and kept all service related actions separate. My problem with this was that returning from within the promise never actually returned me anything. I had to actually return the $http service call to get the promise. The only way I was able to make all of this work was if I called .then from within the controller.
//controller
angular.module('module')
.controller('forgotPasswordCtrl', ['forgotPasswordService', function(forgotPasswordService) {
forgotPasswordService.forgotPassword(emailAddress).then(function() {
//do stuff
}
}]);
//service
angular.module('module')
.service('forgotPasswordService', ['$http', function($http){
this.forgotPassword = function(emailAddress) {
return $http(request);
};
}]);
This just feels a little wrong to me because the controller now depends on receiving a promise back from the service. I may just be overthinking this but I would like a second opinion.
Is this considered acceptable/good practice? Is there an alternative to this which would allow me to achieve the encapsulation I'm looking for?
Thanks again.
I've interfaced with the $http from the controller in two slightly different ways.
Like you it felt wrong returning the $http from the service and interfacing with it.
So first I created services and passed in a success method and an error method (callbacks).
// Service
angular.module('module')
.service('forgotPasswordService', ['$http', function($http) {
function sendForgotPasswordEmail(emailAddress, success, error){
$http.post('/api/v1/resetpassword', {emailAddress:emailAddress})
.then(success, error);
}
return {
sendForgotPasswordEmail: sendForgotPasswordEmail
}
}]);
// Controller
angular.module('module')
.controller('forgotPasswordCtrl', ['forgotPasswordService', function(forgotPasswordService) {
forgotPasswordService.sendForgotPasswordEmail(emailAddress,
function(response){ //success
// notify user of success
},
function(response){ // error
// notify user of error
});
}]);
This worked great. I created an large application this way, but as I started on my second large angular project I wondered why I was hiding the $http's promise?
By passing back the promise, I can use other libraries that support promises. With my first approach I can't leverage other libraries promise support.
Passing back the $http promise
// Service
angular.module('module')
.service('forgotPasswordService', ['$http', function($http) {
function sendForgotPasswordEmail(emailAddress){
return $http.post('/api/v1/resetpassword', {emailAddress:emailAddress});
}
return {
sendForgotPasswordEmail: sendForgotPasswordEmail
}
}]);
// Controller
angular.module('module')
.controller('forgotPasswordCtrl', ['forgotPasswordService', function(forgotPasswordService) {
forgotPasswordService.sendForgotPasswordEmail(emailAddress)
.then(
function(response){ //success
// notify user of success
},
function(response){ // error
// notify user of error
});
}]);
I deleted my original answer, and I feel like a dork for stating that you could do it the other way. When I went back and checked my original code back when I first started angular, I found that I was calling then() twice in my application - once in my service where I returned the data, and once in my controller because calling $http(request).then() returns a promise.
The fact is, you're dealing with an asynchronous call. Suppose in your controller, you wanted to do this:
$scope.foo = myService.getFoo(); // No then()
The XmlHttpRequest inside the $http in getFoo() is an asynchronous call, meaning that it calls it and moves on. The synchronous option is deprecated. It's bad practice to make a blocking synchronous HTTP call because it will seize up your UI. This means you should use a callback when the data is ready, and the promise API is made just for that.
If you absolutely do not want to use the then() in your controller, I suppose you could probably pass your scope binding parameters to your service and let your service update them in your then call. I haven't tried this, and because it's asynchronous, I'm not sure if angular will know to call a digest() so you may need to call a $scope.$apply() if the values don't update. I don't like this, because I think the control of the values in the scope should be handled by the controller and not the service, but again - it's your personal preference.
Sorry for leading you astray with my initial answer - I ran into the same question you had, but when I looked back - I saw I used a silly solution.
-- Relevant statements in original answer --
Consider the following:
Where do you want your error handling for the call and who needs to know about it?
Do you need to handle specific failures in a particular controller or can they all be grouped together to one error handler? For some apps, I like to display the errors in a particular place rather than in a general modal dialog, but it's acceptable to create a service to handle all errors and pop them up for the user.
Where do you want to handle your progress/busy indicator?
If you have an interceptor wired up for all http calls and broadcasting an event to show/hide the busy indicator, then you don't need to worry about handling the promise in the controller. However some directives will use the promise to show a busy indicator which requires you to bind it to the scope in the controller.
To me, the decision is determined by the requirements and by personal choice.
Try using a callback like so:
angular.module('module')
.service('forgotPasswordService', ['$http', function($http) {
var recovery = function(request, callback) {
$http(request).then(function(response) {
callback(response);
})
}
return { recovery: recovery }
}]);
Then you would call it like this:
forgotPasswordService.recovery('http://...', function(response){
console.log(response);
})

AngularJS - Wait to Load data to display the view

I am really new to AngularJS and after reading several questions and some articles I am a little confused about the correct way to load data and wait till its loaded to display the view.
My controller looks like this
app.controller('ResultsController', ['$scope','$http', '$routeParams', function($scope, $http, $routeParams) {
$scope.poll = {};
$scope.$on('$routeChangeSuccess', function() {
showLoader();
$http.get("rest/visualizacion/" + $routeParams.id)
.success(function(data) {
$scope.poll = data;
hideLoader();
})
.error(function(data) {
// Handle error
});
});
}]);
I have seen there are people who create a service for $http calls, is it necessary? Why is it better?
The appropriate way to do that is to use the resolve property of the route. From the documentation:
resolve - {Object.<string, function>=} - An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated. If all the promises are resolved successfully, the values of the resolved promises are injected and $routeChangeSuccess event is fired. If any of the promises are rejected the $routeChangeError event is fired. The map object is:
key – {string}: a name of a dependency to be injected into the controller.
factory - {string|function}: If string then it is an alias for a service. Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before its value is injected into the controller. Be aware that ngRoute.$routeParams will still refer to the previous route within these resolve functions. Use $route.current.params to access the new route parameters, instead.
So, if you want poneys to be retrieved from the backend before the router goes to the poney list page, you would have
resolve: {
poneys: function($http) {
return $http.get('/api/poneys').then(function(response) {
return response.data;
)};
}
}
And your controller would be defined as
app.controller('PoneyListCtrl", function($scope, poneys) {
$scope.poneys = poneys;
// ...
});
Of course, you could also put the code making the $http call and returning a list of poneys in a service, and use that service in the resolve.

The promise of a promise again (Angular JS)

Updated with HTTP and initial code based on requests/Please look at the bottom of the post:
I've been posting several questions on my AngularJS learning curve of late and the SO community has been fantastic. I've been a traditional C programmer when I used to program and have recently started writing my own ionic/Angular JS app. I'm struggling with the promise version of traditional async calls when it comes to converting a custom function to a promise. I don't think I really understood and I find various examples very contrived. I'd appreciate some help. I have some code which is not working, and I have some conceptual questions:
Let's take this simple function:
angular.module('zmApp.controllers').service('ZMDataModel', function() { return { getMonitors: function () { return monitors; } }
getMonitors is a simple function that basically returns an array of monitors. But here is the rub: When the app first starts, I call an http factory that does an http get and goes about populating this monitor list. This http factory is different from this service but invokes a setMonitor method in this service to populate the array. When the array is populated, a variable called 'monitorsLoaded' is set to 1. When this variable is set to 1, I know for sure monitors is loaded.
Now, I have a view with a controller called "MontageCtrl". I want to wait for the monitors to load before I show the view. In a previous post, one person suggested I use route resolve, but I had to first convert my getMonitors to a promise. So here is what I did:
angular.module('zmApp.controllers').service('ZMDataModel', function($q) {
getMonitors: function () {
var _deferred = $q.defer();
if (monitorsLoaded!=0)
{
console.log ("**** RETURNING MONITORS *****");
_deferred.resolve(monitors);
}
console.log ("*** RETURNING PROMISE ***");
return _deferred.promise;
},
Next up, in app.js I connected the route as follows:
.state('app.montage', {
data: {requireLogin:false},
resolve: {
message: function(ZMDataModel)
{
console.log ("Inside app.montage resolve");
return ZMDataModel.getMonitors();
}
},
Finally I modified my controller to grab the promise as such:
angular.module('zmApp.controllers').controller('zmApp.MontageCtrl', function($scope,$rootScope, ZMHttpFactory, ZMDataModel,message) {
//var monsize =3;
console.log ("********* Inside Montage Ctrl");
It seems based on logs, I never go inside Montage Ctrl. Route resolve seems to be waiting for ever, whereas my logs are showing that after a while, monitorLoaded is being set to 1.
I have several conceptual questions:
a) In function getMonitors, which I crafted as per examples, why do people return a _deferred.promise but only assign a _deferred.resolve? (i.e. why not return it too?). Does it automatically return?
b) I noticed that if I moved var _deferred definition to my service and out of its sub function, it did work, but the next view that had the same route dependency did not. I'm very confused.
c) Finally I ready somewhere that there is a distinction between a service and a factory when it comes to route resolve as a service is only instantiated once. I am also very confused as in some route resolve examples people use when, and I am using .state.
At this stage, I'm deep into my own confusion. Can someone help clarify? All I really want is for various views to wait till monitorsLoaded is 1. And I want to do it via route resolves and promises, so I get the hang of promises once and for all.
Added: Here is the HTTP factory code as well as the app.run code that calls this when the app first starts. FYI, the http factory works well - the problems started when I crafted ZMDataModel - I wanted this to be a central data repository for all controllers to use -- so they did not have to call HTTP Factory each time to access data, and I could control when HTTP factory needs to be called
angular.module('zmApp.controllers').factory('ZMHttpFactory', ['$http', '$rootScope','$ionicLoading', '$ionicPopup','$timeout','ZMDataModel',
function($http, $rootScope, $ionicLoading, $ionicPopup, $timeout,ZMDataModel) {
return {
getMonitors: function() {
var monitors = [];
var apiurl = ZMDataModel.getLogin().apiurl;
var myurl = apiurl+"/monitors.json";
return $http({
url: myurl,
method: 'get'
}) //http
.then(function(response) {
var data = response.data;
//console.log("****YAY" + JSON.stringify(data));
// $rootScope.$broadcast ('handleZoneMinderMonitorsUpdate',monitors);
$ionicLoading.hide();
ZMDataModel.setMonitors(data.monitors);
ZMDataModel.setMonitorsLoaded(1);
//monitors = data.monitors;
return ZMDataModel.getMonitors();
},
function (result)
{
console.log ("**** Error in HTTP");
$ionicLoading.hide();
ZMDataModel.setMonitorsLoaded(1);
//$ionicPopup.alert ({title: "Error", template:"Error retrieving Monitors. \nPlease check if your Settings are correct. "});
return ZMDataModel.getMonitors();
}
); //then
}, //getMonitors
And here is the code in app.run that first calls this:
.run(function($ionicPlatform, $ionicPopup, $rootScope, $state,ZMDataModel, ZMHttpFactory)
{
ZMDataModel.init();
var loginData = ZMDataModel.getLogin();
if ( loginData.username && loginData.password && loginData.url && loginData.apiurl)
{
console.log ("VALID CREDENTIALS. Grabbing Monitors");
// this calls http factory getMonitors that eventually populated the ZMDataModel
// monitors array and sets monitorsLoaded to 1
ZMHttpFactory.getMonitors();
}
}
I finally solved all the problems. There were various issues with my initial attempts. My final resolved solution is here Am I returning this promise correctly?
The learnings:
a) Separating the HTTP get into a factory and the data model into another service was unnecessarily complicating life. But that separation was not the problem. Infact, the way the promise was coded above, on first run, if monitorsLoaded was 0, it would simply return the deferred promise and there was no ".success" or similar construct for me to get into the resolve code block again.
b) The biggest thing that was making me run around in loops was deferring or rejecting was simply setting a state. the return always has to be the promise - and it would return the state you set. I assumed return d.promise always means returning "in progress".

$scope not updating when set from $http promise context

I'm trying to chain several $http calls, and then display the response of the last $http call. The calls all work correctly and the proper response is displayed, but only for the very first time I run the chain of calls. On subsequent runs, it makes it through the $http calls, but does not update the view with the latest response.
I can see in both Firefox and Chrome console network logs, that all the calls within the chain are getting executed (even on subsequent runs of the chain). In debugging the issue, I've replaced the $http calls with simple data assignment and everything works - including updates to the view - so at this point I'm mostly confident it's related to the $http/promise. Can anyone shed some light on what I'm doing wrong, or pointers on where to look as I start to read up on Angular.
Thanks.
I'm on Angular version 1.2.16. and here's my code:
//controller
function($scope, $http) {
$http.get('http://localhost:8080/call/one')
.then(function(responseOne) { return responseOne.data })
.then(function(dataFromCallOne) {
return $http.jsonp('http://localhost:8080/call/two')
.then(function(responseTwo) {
$scope.results = responseTwo.data
});
});
}
In my view, I have
<div ng-controller="MyCtrl">
{{results}}
<button ng-click="makeCall()">Call</button>
</div>
I've tried, the following but still no updates to my view, both methods work for initial display of results, but not on subsequent updates:
if(!$scope.$$phase) {
$scope.$apply()
}
and assigning chain results to $scope.result
$scope.results = $http.get('http://localhost:8080/call/one')
.then(function(responseOne) { return responseOne.data })
.then(function(dataFromCallOne) {
return $http.jsonp('http://localhost:8080/call/two')
.then(function(responseTwo) { return responseTwo.data });
});
$scope.results.then(function(data) {
$scope.results = data
});
I found my error. In my chain of $http calls, one of them is an $http.jsonp() call. When using JSONP, Angular requires a callback param - specifically called JSON_CALLBACK. This JSON_CALLBACK, then gets replace by an internally generated, unique callback name, right before the request is made.
?callback=JSON_CALLBACK --> ?callback=angular.callbacks._0
?callback=JSON_CALLBACK --> ?callback=angular.callbacks._1
?callback=JSON_CALLBACK --> ?callback=angular.callbacks._2
This was problematic for me since my jsonp request is to an OAuth provider that required signing the complete url (including the callback param). So initially I would sign the url with callback=JSON_CALLBACK, but Angular would turn it to something like callback=angular.callbacks._#. I would get an OAuth error since the signature and actual url didn't jive. Anyways, at some point I hard coded callback=angular.callbacks._0, just so I could get my OAuth client code working, but I forgot all about it.
Now subsequent calls to $http.jsonp() with non-unique callback params were not being processed after the response was returned. That's why my $scope is not updated after $http promise.
My hack at the moment is to continue using the hard coded callback=angular.callbacks._#, but with incrementing numbers.

Categories