AngularJS $timeout within a resource factory - angularjs

I currently have an angular application which upon user login calls a service to begin a server call to refresh a count, only allowing for a server side return if the user is authenticated.
resource.approvalsCount = 0;
var approvalsCountTimer;
resource.getApprovalsCount = function (username) {
return resource.query({
username: username,
q: 'approvalsCount'
}).$then(function (response) {
resource.approvalsCount = response.data.count;
approvalsCountTimer = $timeout(resource.getApprovalsCount, 3000);
return resource.approvalsCount;
});
};
When a user logs out I am attempting to cancel that counter otherwise the server will return a 401 unauthorized error by calling a function based on the resource:
resource.cancelTimers = function () {
$timeout.cancel(approvalsCountTimer);
}
The issue is that the counter continues to run even after I call the cancel upon the $timeout which returns a 401 from the server. Console logging out the return the cancel function returns true (cancel has worked). I have tried several different placements of the begin of the $timeout to no avail, is there a way to ensure that all of the $timeouts are canceled? I don't understand what I am missing in this configuration.
EDIT
angular.module('resources.approvals.approvals', ['ngResource'])
.factory('Approvals', ['$timeout', '$resource', function ($timeout, $resource) {
var resource = $resource('/approvals/:username/:requestID', {}, {
'update': {
method: 'PUT'
}
});
resource.approvalsCount = 0;
var approvalsCountTimer;
resource.getApprovalsCount = function (username) {
return resource.query({
username: username,
q: 'approvalsCount'
}).$then(function (response) {
resource.approvalsCount = response.data.count;
approvalsCountTimer = $timeout(resource.getApprovalsCount, 3000);
return resource.approvalsCount;
});
};
resource.cancelTimers = function () {
$timeout.cancel(approvalsCountTimer);
};
return resource;
}]);

I think your code looks good. It got to be something else.
I simplified a bit and you can see it on the demo. it simulates the http call every half second and the cancelTimes will be called in 4 seconds.
app = angular.module('app', []);
app.factory('Approvals', ['$timeout', function ($timeout) {
var resource = {};
resource.approvalsCount = 0;
var approvalsCountTimer;
resource.getApprovalsCount = function (username) {
console.log(approvalsCountTimer);
approvalsCountTimer = $timeout(resource.getApprovalsCount, 500);
};
resource.cancelTimers = function () {
console.log("stopped");
$timeout.cancel(approvalsCountTimer);
};
return resource;
}]);
function Ctrl($scope, $timeout, Approvals) {
Approvals.getApprovalsCount();
$timeout(Approvals.cancelTimers, 4000)
}

Related

Using an Angular Service for async HttpRequests - how to callback?

I might be totally confused on how to properly use callback methods for ajax calls in angular. I have the following angular app and cannot figure out how to display the testUser object only after the ajax call is successfully completed.
I have an ng controller like so:
Controllers.controller('mainController', ['$scope','UserService', function ($scope, UserService) {
...
$scope.testUser = UserService.getTestUser();
...
}
The UserService is defined like so:
Services.service('UserService', function () {
...
this.getTestUser = function() {
...
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
return JSON.parse(xmlhttp.responseText);
}
};
xmlhttp.open('GET',url,false); //async set to false
xmlhttp.send();
}
...
}
Currently, the $scope.testUser is 'undefined' and blank on the page because it is being displayed before the ajax call completes. You can see in my service function that I have async set to false, but it doesnt seem to matter.
I've confirmed the ajax call does eventually return a populated user object. What am I missing to make the page display $scope.testUser only when its been successfully retrieved?
Thanks to Slava and georgeawg. I changed to the following and everything works great!
Controllers.controller('mainController', ['$scope','UserService', function ($scope, UserService) {
...
UserService.getTestUser.async().then(function(testUser) {
$scope.testUser = testUser;
};
...
}
And on the service side I have this:
Services.service('UserService', function ($http) {
...
this.getTestUser = {
async: function() {
var promise = $http.get(url).then(function(response) {
return response.data;
};
return promise;
}
}
Thank you!

Reuse data from $http in factory

I understand that the appropriate method to share data between controllers in Angular.js is by using Factories or Services.
app.controller('Controller1', function($scope, DataService) {
DataService.getValues().then(
function(results) {
// on success
console.log('getValues onsuccess');
});
});
app.controller('Controller2', function($scope, DataService) {
DataService.getValues().then(
function(results) {
// on success
console.log('getValues onsuccess');
});
});
app.factory('DataService', function($http) {
var getValues = function() {
console.log('making http request');
return $http.get("/api/getValues");
};
return {
getValues: getValues
}
});
I have two controllers calling the same method in a factory twice
and this is perfectly fine and everything is working as it should. My only concer is that it seems a bit unecessary to make the same request twice? Would the use of $broadcast be a better approach?
Or could i structure my code differenty so that the service is called only once?
You could store the results of the request in the factory and retrieve those instead.
app.factory('DataService', function($http) {
var values;
var requestValues = function() {
console.log('making http request');
$http.get("/api/getValues").then(
function(results){
values = results;
});
};
var getValues = function() {
return values;
};
return {
requestValues : requestValues,
getValues: getValues
}
});
If your data is somekind of static and may not change very often over time you could do something like:
app.factory('DataService', function($http) {
self = this;
this.isLoaded = false;
this.results;
this.getValues = function() {
console.log('making http request');
$http.get("/api/getValues").then(
function(results) {
// on success
console.log('getValues onsuccess');
self.isLoaded = true
this.results = results;
return results;
})
);
};
})
And in the controller:
app.controller('Controller2', function($scope, DataService) {
if(!DataService.isLoaded){
results = DataService.getValues()
}else{
results = DataService.results;
}
});
You should consider caching in your DataService. Add a variable to hold the result from the http service and a time-stamp variable to store the time it was retrieved.
If a second call to the service is within a preset time period (lets say, 5 seconds), then http call is not made and data from the cache is returned.
app.factory('DataService', function($http) {
var cachedValue = null;
var lastGet = null;
var getValues = function() {
var timeNow = new Date();
if (cachedValue == null || ((timeNow - lastGet) < 5000)) {
console.log('making http request');
lastGet = timeNow;
cachedValue = $http.get("/api/getValues");
} else console.log('returning cached value');
return cachedValue;
};
return {
getValues: getValues
}
});

AngularJS call scope function to 'refresh' scope model

I've been struggling with this for a few days now and can't seem to find a solution.
I have a simple listing in my view, fetched from MongoDB and I want it to refresh whenever I call the delete or update function.
Although it seems simple that I should be able to call a previously declared function within the same scope, it just doesn't work.
I tried setting the getDispositivos on a third service, but then the Injection gets all messed up. Declaring the function simply as var function () {...} but it doesn't work as well.
Any help is appreciated.
Here's my code:
var myApp = angular.module('appDispositivos', []);
/* My service */
myApp.service('dispositivosService',
['$http',
function($http) {
//...
this.getDispositivos = function(response) {
$http.get('http://localhost:3000/dispositivos').then(response);
}
//...
}
]
);
myApp.controller('dispositivoController',
['$scope', 'dispositivosService',
function($scope, dispositivosService) {
//This fetches data from Mongo...
$scope.getDispositivos = function () {
dispositivosService.getDispositivos(function(response) {
$scope.dispositivos = response.data;
});
};
//... and on page load it fills in the list
$scope.getDispositivos();
$scope.addDispositivo = function() {
dispositivosService.addDispositivo($scope.dispositivo);
$scope.getDispositivos(); //it should reload the view here...
$scope.dispositivo = '';
};
$scope.removeDispositivo = function (id) {
dispositivosService.removerDispositivo(id);
$scope.getDispositivos(); //... here
};
$scope.editDispositivo = function (id) {
dispositivosService.editDispositivo(id);
$scope.getDispositivos(); //... and here.
};
}
]
);
On service
this.getDispositivos = function(response) {
return $http.get('http://localhost:3000/dispositivos');
}
on controller
$scope.addDispositivo = function() {
dispositivosService.addDispositivo($scope.dispositivo).then(function(){
$scope.getDispositivos(); //it should reload the view here...
$scope.dispositivo = '';
});
};
None of the solutions worked. Later on I found that the GET request does execute, asynchronously however. This means that it loads the data into $scope before the POST request has finished, thus not including the just-included new data.
The solution is to synchronize the tasks (somewhat like in multithread programming), using the $q module, and to work with deferred objects and promises. So, on my service
.factory('dispositivosService',
['$http', '$q',
function($http, $q) {
return {
getDispositivos: function (id) {
getDef = $q.defer();
$http.get('http://myUrlAddress'+id)
.success(function(response){
getDef.resolve(response);
})
.error(function () {
getDef.reject('Failed GET request');
});
return getDef.promise;
}
}
}
}
])
On my controller:
$scope.addDispositivo = function() {
dispositivosService.addDispositivo($scope.dispositivo)
.then(function(){
dispositivosService.getDispositivos()
.then(function(dispositivos){
$scope.dispositivos = dispositivos;
$scope.dispositivo = '';
})
});
};
Being my 'response' object a $q.defer type object, then I can tell Angular that the response is asynchronous, and .then(---).then(---); logic completes the tasks, as the asynchronous requests finish.

WebSocket callback function multiple invocations

I'm trying to create small web application which uses WebSocket protocol to exchange the data
Here is my angular service
define(function () {
return function (module) {
return module.service("socketService", function($q, $timeout) {
var service = {},
listener = $q.defer(),
socket = {
client: null,
stomp: null
};
service.RECONNECT_TIMEOUT = 30000;
service.SOCKET_URL = "/service/booking";
service.CHAT_TOPIC = "/service/booking/updates";
service.CHAT_BROKER = "/service/booking";
service.receive = function() {
return listener.promise;
};
...
return service;
});
};
});
and also controller code
define(function () {
return function (module) {
socketService.receive().then(null, null, function(message) {
$scope.list.countDownTimer = 0;
if(message.resourceId === parseInt($routeParams.resourceId)) {
$scope.list.bookings = [];
}
...
});
My problem is that promise.then() callback function is invoked many times and the amount of this invocations is not repeatable: it might be 2 times, 27 or even 110.
One more thing that I should add is that I think that is somehow dependent with amount of users who tests and clicks on the application
Could anyone help?
Thank you in advance...

How to cancel $resource requests

I'm trying to figure out how to use the timeout property of a $resource to dynamically cancel pending requests. Ideally, I'd like to just be able to cancel requests with certain attributes (based on the params sent), but it seems this may not be possible. In the meantime, I'm just trying to cancel all pending requests, and then resetting the timeout promise to allow new requests.
The issue seems to be that the $resource configuration only allows a single, static promise for the timeout value. It makes sense how I could do this if I was making individual $http calls, since I could just pass in new promises for the timeout, but how can this work for a $resource? I have set up an example plunker here: http://plnkr.co/edit/PP2tqDYXh1NAOU3yqCwP?p=preview
Here's my controller code:
app.controller('MainCtrl', function($scope, $timeout, $q, $resource) {
$scope.canceller = $q.defer();
$scope.pending = 0;
$scope.actions = [];
var API = $resource(
'index.html', {}, {
get: {
method: 'GET',
timeout: $scope.canceller.promise
}
}
)
$scope.fetchData = function() {
if ($scope.pending) {
$scope.abortPending();
}
$scope.pending = 1;
$scope.actions.push('request');
API.get({}, function() {
$scope.actions.push('completed');
$scope.pending = 0;
}, function() {
$scope.actions.push('aborted');
});
}
$scope.abortPending = function() {
$scope.canceller.resolve();
$scope.canceller = $q.defer();
}
});
Right now, the canceller works when there is a pending request, but I don't seem to be able to reset it - once one request is aborted, all future requests will be aborted as well.
I'm sure I'm missing something, since being able to cancel pending requests seems like a pretty crucial feature of most web applications (at least that I've built).
Thanks
Answer by Gecko IT works for me, but I had to make some modifications in order to:
Enable resource ajax call to be canceled multiple times without need to recreate resource
Make resource backward compatible - This means that there is no need to change any application (Controller) code except resource factory
Make code JSLint compliant
This is complete service factory implementation (you just need to put proper module name):
'use strict';
/**
* ResourceFactory creates cancelable resources.
* Work based on: https://stackoverflow.com/a/25448672/1677187
* which is based on: https://developer.rackspace.com/blog/cancelling-ajax-requests-in-angularjs-applications/
*/
/* global array */
angular.module('module_name').factory('ResourceFactory', ['$q', '$resource',
function($q, $resource) {
function abortablePromiseWrap(promise, deferred, outstanding) {
promise.then(function() {
deferred.resolve.apply(deferred, arguments);
});
promise.catch(function() {
deferred.reject.apply(deferred, arguments);
});
/**
* Remove from the outstanding array
* on abort when deferred is rejected
* and/or promise is resolved/rejected.
*/
deferred.promise.finally(function() {
array.remove(outstanding, deferred);
});
outstanding.push(deferred);
}
function createResource(url, options, actions) {
var resource;
var outstanding = [];
actions = actions || {};
Object.keys(actions).forEach(function(action) {
var canceller = $q.defer();
actions[action].timeout = canceller.promise;
actions[action].Canceller = canceller;
});
resource = $resource(url, options, actions);
Object.keys(actions).forEach(function(action) {
var method = resource[action];
resource[action] = function() {
var deferred = $q.defer(),
promise = method.apply(null, arguments).$promise;
abortablePromiseWrap(promise, deferred, outstanding);
return {
$promise: deferred.promise,
abort: function() {
deferred.reject('Aborted');
},
cancel: function() {
actions[action].Canceller.resolve('Call cancelled');
// Recreate canceler so that request can be executed again
var canceller = $q.defer();
actions[action].timeout = canceller.promise;
actions[action].Canceller = canceller;
}
};
};
});
/**
* Abort all the outstanding requests on
* this $resource. Calls promise.reject() on outstanding [].
*/
resource.abortAll = function() {
for (var i = 0; i < outstanding.length; i++) {
outstanding[i].reject('Aborted all');
}
outstanding = [];
};
return resource;
}
return {
createResource: function (url, options, actions) {
return createResource(url, options, actions);
}
};
}
]);
Usage is the same as in Gecko IT example. Service factory:
'use strict';
angular.module('module_name').factory('YourResourceServiceName', ['ResourceFactory', function(ResourceFactory) {
return ResourceFactory.createResource('some/api/path/:id', { id: '#id' }, {
create: {
method: 'POST'
},
update: {
method: 'PUT'
}
});
}]);
Usage in controller (backward compatible):
var result = YourResourceServiceName.create(data);
result.$promise.then(function success(data, responseHeaders) {
// Successfully obtained data
}, function error(httpResponse) {
if (httpResponse.status === 0 && httpResponse.data === null) {
// Request has been canceled
} else {
// Server error
}
});
result.cancel(); // Cancels XHR request
Alternative approach:
var result = YourResourceServiceName.create(data);
result.$promise.then(function success(data, responseHeaders) {
// Successfully obtained data
}).catch(function (httpResponse) {
if (httpResponse.status === 0 && httpResponse.data === null) {
// Request has been canceled
} else {
// Server error
}
});
result.cancel(); // Cancels XHR request
Further improvements:
I don't like checking if request has been canceled. Better approach would be to attach attribute httpResponse.isCanceled when request is canceled, and similar for aborting.
(for Angular 1.2.28+)Hello All , I just wanted to make that easy to understand , how I handled that issue is as follows :
Here I declare timeout parameter
$scope.stopRequestGetAllQuestions=$q.defer();
then in I use it as follows
return $resource(urlToGet, {}, {get:{ timeout: stopRequestGetAllQuestions.promise }});
and if I want to stop previous $resource calls I just resolve this stopRequestGetAllQuestions object that is all.
stopRequestGetAllQuestions.resolve();
but if I want to stop previous ones and start a new one $resource call then I do this after stopRequestGetAllQuestions.resolve();:
stopRequestGetAllQuestions = $q.defer();
There are quite a lot of examples currently out there.
The following two I've found quite informative:
THis one shows an example how to deal with both $resource and $http requests:
https://developer.rackspace.com/blog/cancelling-ajax-requests-in-angularjs-applications/
and
This one is simpler and is only for $http:
http://odetocode.com/blogs/scott/archive/2014/04/24/canceling-http-requests-in-angularjs.aspx
Hi I made a custom handler based on https://developer.rackspace.com/blog/...
.factory('ResourceFactory', ["$q", "$resource", function($q, $resource) {
function createResource(url, options, actions) {
var actions = actions || {},
resource,
outstanding = [];
Object.keys(actions).forEach(function (action) {
console.log(actions[action]);
var canceller = $q.defer();
actions[action].timeout = canceller.promise;
actions[action].Canceller = canceller;
});
resource = $resource(url, options, actions);
Object.keys(actions).forEach(function (action) {
var method = resource[action];
resource[action] = function () {
var deferred = $q.defer(),
promise = method.apply(null, arguments).$promise;
abortablePromiseWrap(promise, deferred, outstanding);
return {
promise: deferred.promise,
abort: function () {
deferred.reject('Aborted');
},
cancel: function () {
console.log(actions[action]);
actions[action].Canceller.resolve("Call cancelled");
}
};
};
});
/**
* Abort all the outstanding requests on
* this $resource. Calls promise.reject() on outstanding [].
*/
resource.abortAll = function () {
for (var i = 0; i < outstanding.length; i++) {
outstanding[i].reject('Aborted all');
}
outstanding = [];
};
return resource;
}
return {
createResource: function (url, options, actions) {
return createResource(url, options, actions);
}
}
}])
function abortablePromiseWrap(promise, deferred, outstanding) {
promise.then(function () {
deferred.resolve.apply(deferred, arguments);
});
promise.catch(function () {
deferred.reject.apply(deferred, arguments);
});
/**
* Remove from the outstanding array
* on abort when deferred is rejected
* and/or promise is resolved/rejected.
*/
deferred.promise.finally(function () {
array.remove(outstanding, deferred);
});
outstanding.push(deferred);
}
//Usage SERVICE
factory("ServiceFactory", ["apiBasePath", "$resource", "ResourceFactory", function (apiBasePath, $resource, QiteResourceFactory) {
return ResourceFactory.createResource(apiBasePath + "service/:id", { id: '#id' }, null);
}])
//Usage Controller
var result = ServiceFactory.get();
console.log(result);
result.promise.then(function (data) {
$scope.services = data;
}).catch(function (a) {
console.log("catch", a);
})
//Actually cancels xhr request
result.cancel();
One solution is to re-craete the resource every time you need it.
// for canceling an ajax request
$scope.canceler = $q.defer();
// create a resource
// (we have to re-craete it every time because this is the only
// way to renew the promise)
function getAPI(promise) {
return $resource(
'index.html', {}, {
get: {
method: 'GET',
timeout: promise
}
}
);
}
$scope.fetchData = function() {
// abort previous requests if they are still running
$scope.canceler.resolve();
// create a new canceler
$scope.canceler = $q.defer();
// instead of using "API.get" we use "getAPI().get"
getAPI( $scope.canceler.promise ).get({}, function() {
$scope.actions.push('completed');
$scope.pending = 0;
}, function() {
$scope.actions.push('aborted');
});
}
In our attempt to solve this task we got to the following solution:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Cancel resource</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular-resource.js"></script>
<script>
angular.module("app", ["ngResource"]).
factory(
"services",
["$resource", function($resource)
{
function resolveAction(resolve)
{
if (this.params)
{
this.timeout = this.params.timeout;
this.params.timeout = null;
}
this.then = null;
resolve(this);
}
return $resource(
"http://md5.jsontest.com/",
{},
{
MD5:
{
method: "GET",
params: { text: null },
then: resolveAction
},
});
}]).
controller(
"Test",
["services", "$q", "$timeout", function(services, $q, $timeout)
{
this.value = "Sample text";
this.requestTimeout = 100;
this.call = function()
{
var self = this;
self.result = services.MD5(
{
text: self.value,
timeout: $q(function(resolve)
{
$timeout(resolve, self.requestTimeout);
})
});
}
}]);
</script>
</head>
<body ng-app="app" ng-controller="Test as test">
<label>Text: <input type="text" ng-model="test.value" /></label><br/>
<label>Timeout: <input type="text" ng-model="test.requestTimeout" /></label><br/>
<input type="button" value="call" ng-click="test.call()"/>
<div ng-bind="test.result.md5"></div>
</body>
</html>
How it works
$resource merges action definition, request params and data to build a config parameter for an $http request.
a config parameter passed into an $http request is treated as a promise like object, so it may contain then function to initialize config.
action's then function may pass timeout promise from params into the config.
Please look at "Cancel Angularjs resource request" for details.

Resources