I followed the excellent advice dispatched here (Server polling with AngularJS), but [think I] have a need to sometimes cancel the polling, to reinstate it later.
Specifically, I have a list of data. The client polls the server every 5 seconds with a timestamp of the "last synchronization" -- the last time that it conferred with the server. The server responds with any changes since that timestamp.
Sometimes the client may make a change itself, sending a PUT to the server.
I'm having an issue with the PUT request, I think, interfering with the poll (or vice-versa), causing data to get out of sync. I'd like to test this by canceling the poll until the PUT request has been approved.. but I just can't get to a place where the polling function can successfully call itself each time; issue a promise that's cancellable; and be restarted externally after it's canceled.
I got a little close with a service ("pulseService"), but I can't get all the way. It looks like this, but fails with "cannot read property 'poller' of undefined":
myModule.factory('pulseService', function($http, $rootScope, $timeout) {
$rootScope.pulsePromise = null;
var obj = {
poller: function() {
var thing = this;
console.log("Here I am!");
var semaphore = new Date().getTime();
var query = {"timestamp": {'$gt': semaphore}};
query = JSON.stringify(query);
$http({method: 'GET', url: '/registrants', data: query}).
success(function(data, status, headers, config) {
$rootScope.error = false;
$rootScope.$broadcast('pollFinished', data);
$rootScope.pulsePromise = $timeout(thing.poller, 5000);
}).
error(function(data, status, headers, config) {
$rootScope.error = true;
semaphore = new Date().getTime();
$rootScope.pulsePromise = $timeout(thing.startPolling, 15000);
});
}(),
startPolling: function() {
console.log(this);
this.poller;
}
};
return obj;
});
By request, here's a simplified version of my controller.. It might have a little kruft in it but I tried to simplify out stuff:
function regCtrl($scope, $http, $rootScope, $timeout, pulseService) {
// ...
// Doing stuff to initialize and gather data into $scope.attendees
$scope.$on( 'pollFinished', function( event, data ) {
var found = false;
angular.forEach(data, function(resultVal, resultKey) {
while (found === false) {
angular.forEach($scope.attendees, function(attendeeVal, attendeeKey) {
if (attendeeVal.id == resultVal.id) {
$scope.attendees[attendeeKey] = resultVal;
found = true;
}
});
}
found = false;
});
});
// .. Logic for pushing a change to the server
// .....
$timeout.cancel($rootScope.pulsePromise);
$http({method: 'PUT', url: '/registrants/'+attendee.id, data: query }).
success(function(data, status, headers, config) {
attendee.isHere = data.isHere;
console.log("rerunning");
}).
error(function(data, status, headers, config) {
$scope.error = true;
});
// ...
var semaphore = new Date().getTime();
// Kickoff the polling process
pulseService.startPolling();
}
regCtrl.$inject = ['$scope','$http','$rootScope','$timeout','pulseService'];
I think the specific error you're getting is because, when you do $timeout(thing.startPolling, 15000), startPolling is unbound. So "this" inside startPolling is undefined.
I think you could just replace both $timeout calls with $timeout(obj.poller, ...) and get rid of startPolling.
Or you can just bind the method like $timeout(thing.poller.bind(thing), 5000).
Related
In my app, I have an event that listens for new messages sent to a user. Upon receiving the event, it runs a factory function to retrieve messages. However, it seems as though it is always 1 event behind (ie, event 1 data doesn't display until event 2 occurs).
I have a feeling this has to do with the digest cycle. I have tried $scope.$apply, $timeout to no avail. Hopefully I have been clear enough.
$scope.retrieveMessages = function(){
Conversations.retrieveConversations($scope.authentication.uid)
.then(function(success){
$scope.messageList = success;
}, function(error){
console.log(error);
});
};
$scope.$on('$RECEIVED_MESSAGE', function (event, data) {
$scope.retrieveMessages();
$scope.$apply();
});
Service
angular
.module('conversations')
.factory('EventEmitter', ['$rootScope',
function($rootScope) {
var factory = {
newMessage: function() {
$rootScope.$broadcast('$RECEIVED_MESSAGE');
}
};
return factory;
}]);
Function in controller that watches firebase for changes
var notificationsRef = new Firebase(config.firebaseRef + 'notifications/' + $scope.authentication.uid);
notificationsRef.limitToLast(1).on('child_added', function(childSnapshot, prevChildKey) {
var snapshot = childSnapshot.val();
if(snapshot.type === 'Conversation'){
EventEmitter.newMessage();
};
})
.catch(function(error) {
console.error("Error:", error);
});
Conversations Factory (omitted definition and other methods for brevity)
retrieveConversations: function(uid){
var deferred = $q.defer();
var request = {
uid: uid
};
$http.post(config.serverRef + '/conversations', request)
.success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
deferred.resolve(data);
})
.error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
deferred.reject(status);
});
return deferred.promise;
},
Issue was not with the code but timing and execution. Calls were happening faster than the re-indexing of firebase data to elasticsearch. Solved with $timeout(function(){$scope.retrieveMessages()}, 1000).
I am working on blogging app . i want to save article list on SqlLite as well. I need to fetch all blogs in once. ( having more than 2000) blogs .
following is my controller code.
var promise= userService.getArticles();
promise.then(function(data) {
$scope.articles = data;
}, function(error) {
console.log('Failed for some reason:' + error);
});
and factory code is
angular.module('starter.controllers')
.factory('userService', function($http,$q) {
var articleList = [];
return {
getArticles : function() {
var deferred = $q.defer();
$http({
url: "https://www.website.com/getallinfo?access_token=94529e5d",
data: { starLimit: 0, endLimit: 150,created_date: 0 },
method: 'POST',
withCredentials: true,
}).success(function (data, status, headers, config) {
deferred.resolve(data);
}).error(function (err) {
deferred.reject(error); //
})
return deferred.promise;
},
}
which is returing result.
I need to save that data in sqllite as well. Also i want to show data as offline.
I am not sure how to proceed this. Kindly help.
Thanks
Most offline applications use the local storage as a cache and updating that if there's a connection available.
An easy way to do this is to:
Grab the articles from the local storage and assign it to a $scope variable (this can be undefined)
Request the articles as normal from the server
On a successful callback overwrite the local storage and reassign the scope variable.
Exampe code:
// The article service
myApp.factory('articles', function($q, $timeout, $window, loremIpsum) {
// Initialize by grabbing the articles from the local cache (if any)
var items = angular.fromJson($window.localStorage.getItem('myArticles'));
// Construct our service
var service = {
items: items,
refresh: function() {
// Sync with the server
var defer = $q.defer();
// For this demo I'm using a timeout (this also allows for some artificial lag).
// Replace this with your $http calls.
$timeout(function() {
// Here I'm generating some total random items that we're resolving
// Also not needed in your app
var c = 100, result = [];
while (c--) {
result.push({
title: loremIpsum.getRandomLine(2),
description: loremIpsum.getRandomLine(15)
});
}
service.items = result;
defer.resolve(result);
// Store the returned result from the server in the local storage
$window.localStorage.setItem('myArticles', angular.toJson(result));
}, 500);
return defer.promise;
}
};
return service;
});
Plunker example can be found here.
I have an http-method that gets some data from a google spreadsheet. I want to add this to the $scope so I can output it in the DOM. Later I might make a timed loop of this so that the $scope get's updated every 5 seconds or so.
I currently run the code in app.run:
angular.module('spreadsheet2angular', []).
run(function($http){
$http({method: 'GET', url: 'http://cors.io/spreadsheets.google.com/feeds/cells/0Aq_23rNPzvODdFlBOFRYWlQwUFBtcXlGamhQeU9Canc/od6/public/values?alt=json'}).
success(function(data, status, headers, config) {
var entries = data.feed.entry;
var phraces = [];
entries.forEach(function(entry){
var cell = entry.gs$cell;
if(!phraces[cell.row]){
phraces[cell.row] = {};
}
if(cell.col == 1)
{
phraces[cell.row].name = cell.$t;
}
else if(cell.col == 2)
{
phraces[cell.row].value = cell.$t;
}
});
phraces.forEach(function(phrace){
console.log(phrace);
});
}).
error(function(data, status, headers, config) {
console.log('error');
});
});
I'm new to angular, is this the best place to run it? I would like to run it as something that is easily reusable in different projects.
I think from what you've explained, a service would be perfect. Build it out then inject it in your controller. You can then call/use that service object whenever you would like.
I would use service/factory that returns promise. So we call async service method, get back promise and parse response into controller.
If you think to use the same call in the future, you can write generic method.
By the same way, if you are going to parse response by the same way in the future, the part of logic I would put into the service as well and wrap with $q . So the response still will be promise.
And this is an example I use that might help you to understand what I'm meaning:
app.service('apiService', ['$http', '$q', '$rootScope',
function($http, $q, $rootScope) {
var request = function(method, data) {
var deferred = $q.defer();
var configHttp = {
method: 'POST',
url: config.api + '/' + method
};
if (data !== undefined) {
configHttp.data = data;
}
$http(configHttp).success(function(data, status, headers) {
if (data.error === undefined) {
deferred.resolve(data);
} else {
deferred.reject(data);
}
}).error(function(data, status, headers) {
deferred.reject(data);
});
return deferred.promise;
}
return {
getItem: function() {
return request('get_item');
},
getItemByParams: function(id) {
return request('get_item_by_params', {id: id});
}
};
}
]);
I'm quite new on AngularJS and got stumped on a problem. I'm trying to get a value from a factory and was able to get the data, I attached a console.log() to the function to check, but when I tried to attached these data on a scope and do a check using console.log again, I’m getting a undefined message on the log.
I tried creating an object scope $scope.formWO= {}; and references it there but still I’m getting the same message. I don’t know if there are conflicts between scopes or an issue of displaying the page early before the system read or perform the function. I already read a lot on scopes but for somehow this problem seem to put my development into a complete stop. I don’t know which is which and would like definitely to get enlightened on this.
Here is the trimmed down controller just to give you an idea.
myApp.controller('ordersCtrl',
function ordersCtrl($scope, ngTableParams, dealerData, costElementData, totNoData, $http, $ekathuwa, $rootScope, $location, userService, $filter){
$scope.formWO = {};
$scope.addWorkOrder = function(){
$scope.modalHeader = "ADD NEW WORK ORDER";
$scope.modalType = "Add";
$ekathuwa.modal({
id: "modalWO",
scope: $scope,
templateURL: "./tpl/modal-wo.html"
});
$scope.$watch('formWO.dealer', function(newDealer, oldDealer){
if ( newDealer === oldDealer) {
return;
}
$http.get('api/profile/'+$scope.formWO.dealer).
success(function(data, status, headers, config){
$scope.formWO.outletno = data.outletno;
});
if ($scope.dealer === newDealer) {
return;
}
});
}
$scope.submitAddWorkOrder = function(cost_element, desc, ets, etc, cost_estimate, dealer){
totNoData.getTotNo(function(noData){
$scope.formWO.totno = noData;
});
console.log($scope.formWO.totno);
$scope.tableParams.reload();
}
});
Here is the factory:
myApp.factory('totNoData', function($http, $log){
return {
getTotNo: function(successcb){
$http({method: 'GET', url: 'api/totno'}).
success(function(data, status, headers, config){
successcb(data);
}).
error(function(data, status, headers, config){
$log.warn(data, status, headers, config);
});
}
}
});
What you probably want to do is move the log and, I guess, the reload, inside of the anonymous callback function that you're passing to 'getToNo'. Like:
$scope.submitAddWorkOrder = function(cost_element, desc, ets, etc, cost_estimate, dealer){
totNoData.getTotNo(function(noData){
$scope.formWO.totno = noData;
console.log($scope.formWO.totno);
$scope.tableParams.reload();
});
}
As it is, the log and the reload are happening before the callback is invoked. The order would go something like this:
call the getTotNo method
send the http request
step back out to submitAddWorkOrder
log $scope.formWO.totno (undefined)
call $scope.tableParams.reload();
... event loop / digest
http response received, success method called
callback invoked
$scope.formWO.totno is set
Async for the win!
EDIT:
A better to pattern is to return the promise that is returned from the $http call.
So change the service to:
getTotNo: function(){
return $http({method: 'GET', url: 'api/totno'});
}
And refactor your controller function to:
$scope.submitAddWorkOrder = function(cost_element, desc, ets, etc, cost_estimate, dealer){
totNoData.getTotNo().success(function (data) {
$scope.formWO.totno = noData;
console.log($scope.formWO.totno);
$scope.tableParams.reload();
}).error(function (data, status, headers, config) {
$log.warn(data, status, headers, config);
});
}
I'm using a service in order to share data between controllers. However, the service is returning a promise with cached data even when making new requests. Depending on where the defer instance is created either live data is returned but two-way binding breaks or the two-way binding works but cached data is returned.
How can one prevent the return of a promise with cached data and keep two-way binding?
I've put up a plunker to illustrate the case: http://plnkr.co/edit/SyBvUu?p=preview and for sake of completeness, here is the troublemaking service:
app.service('myService', function($http, $q) {
// When instancing deferred here two way binding works but cached data is returned
var deferred = $q.defer();
this.get = function(userId) {
// When instancing deferred here two way binding breaks but live data is returned
//var deferred = $q.defer();
console.log('Fetch data again using id ', userId);
var url = userId + '.json';
$http.get(url, {timeout: 30000, cache: false})
.success(function(data, status, headers, config) {
deferred.resolve(data, status, headers, config);
})
.error(function(data, status, headers, config) {
deferred.reject(data, status, headers, config);
});
return deferred.promise;
};
});
UPDATE: The problem wasn't that data was cached, it was that I hadn't understood how a data was to be shared and that the shared data can not be a primitive. See my own answer below.
Since $http returns a deferred object what you are doing here is actually overkill. When I changed your service to the following it seems to work fine.
Plunker
app.service('myService', function($http, $q) {
this.get = function(userId) {
console.log('Fetch data again using id ', userId);
var url = userId + '.json';
return $http.get(url, {timeout: 30000, cache: false});
};
});
Edit
To get your controller SecondCtrl to update, the easiest thing to do, while keeping the structure of your code the same, is to broadcast the new data in an event defined in FirstCtrl using $rootScope.$broadcast and capture the broadcasted event in your other controller using $scope.$on. I've updated the Plunker and now your data is in sync.
Modified loadUserFromMyService function in FirstCtrl:
$scope.loadUserFromMyService = function(userId) {
var promise = myService.get(userId);
promise.then(
function(data) {
console.log('UserData', data);
$scope.data = data;
$rootScope.$broadcast('newData', data);
},
function(reason) { console.log('Error: ' + reason); }
);
};
Added in SecondCtrl:
$scope.$on('newData', function (evt, args) {
console.log('dataChanged', args);
$scope.data = args;
});
I came up with simplified solution to share data with the help of Luke Kende. Here is a plunk: http://plnkr.co/edit/JPg1XE?p=preview. See code below.
One important thing is that the shared object isn't a primitive. When I tried different solutions I started with declaring the shared object and assign it null, which is a no-no. Using an empty object makes it work.
var app = angular.module('plunker', []);
// Service
app.service('myService', function($http, $q) {
//object that will be shared between controllers
var serviceData = {
items: []
};
return {
data: serviceData, //pass through reference to object - do not use primitives else data won't update
get: function(url, overwrite) {
if (serviceData.items.length === 0 || overwrite){
$http.get(url, {timeout: 30000})
.success(function(data, status, headers, config) {
//could extend instead of ovewritting
serviceData.items = data;
})
.error(function(data, status, headers, config) {
serviceData.items = {status: status};
});
}
return serviceData;
},
empty: function(){
serviceData.items = [];
},
more: function(){
//do some other operations on the data
}
};
});
// Controller 1
app.controller('FirstCtrl', function( myService,$scope) {
//myService.data is not initialized from server yet
//this way don't have to always use .then() statements
$scope.data = myService.data;
$scope.getTest = function(id){
myService.get('test' + id + '.json',true);
};
$scope.addItem = function() {
$scope.data.items.push({'title': 'Test ' + $scope.data.items.length});
};
$scope.delItem = function() {
$scope.data.items.splice(0,1);
};
});
// Controller 2
app.controller('SecondCtrl', function( myService,$scope) {
//just attach myService.data and go
//calling myService.get() results in same thing
$scope.data = myService.data;
//update the the data from second url -
$scope.getTest = function(id){
myService.get('test' + id + '.json',true);
};
$scope.empty = function(){
myService.empty();
};
});