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).
Related
I have an asynchronous web service which will return status "pending" immediately until it returns either 200 or an error.
This is my code so far:
$http.get('/myweb/services/callService').success(function(response, status, headers, config){
...handling success
}).error(function(err, status, headers, config){
//handling failure
});
//called just after $http.get
$scope.askProgress();
Where askProgress is:
$http.get('/myweb/services/progress').success(function(response, status, headers, config){
console.log(response);
$scope.reportProgress = response.description;
if(response.description < 100){//description is a 0-100 value indicating progress
...updating status...
$timeout(function() {$scope.askProgress();}, 1000); //calling again
}else{
$scope.reportProgressL = "Done!";
}
}).error(function(err, status, headers, config){
alert('Error: '+err+" "+status);
});
My problem is that the first call to askProgress is made before the service returns status "pending" leading to a non consistent progress value.
I'd like for the askProgress function to be called just after the service gives me the first "pending"...is it possible?
Not sure I understand you problem fully but it sounds like you need to be calling your askProgress() function after the first http request returns. If so, have you tried putting the call inside your then() call?
$http
.get('/myweb/services/callService')
.then(function(response, status, headers, config){
return $scope.askProgress();
})
.then(function(progressResponse){
})
.catch(function(error){});
Update
I think you will need to register an $http interceptor to track the state of your request.
Try this:
DEMO
app.js
var app = angular.module('plunker', []);
app.factory('myHttpInterceptor', [ function() {
var pendingRequests = {};
return {
getPendingRequests: function(){
return pendingRequests;
},
request: function(request) {
console.log('*** request made # %s ***', new Date());
pendingRequests[request.url] = true;
console.log('pendingRequests', pendingRequests);
console.log('**************************');
return request;
},
response: function(response) {
console.log('*** response received # %s ***', new Date());
var url = response.config.url;
if(pendingRequests.hasOwnProperty(url)){
delete pendingRequests[url];
}
console.log('pendingRequests', pendingRequests);
console.log('**************************');
return response;
}
};
}]);
app.config(['$httpProvider', function($httpProvider) {
// register our factory as an http interceptor
// in the config phase
$httpProvider.interceptors.push('myHttpInterceptor');
}]);
app.controller('MainCtrl', function($scope, $http, $timeout, myHttpInterceptor) {
var targetUrl = 'big-data.json';
$scope.callService = function(){
console.log('*** call service ***');
return $http.get(targetUrl)
.then(function(){
console.log('********** done, success **********');
})
.catch(function(){
console.log('********** done, error **********');
});
}
$scope.askProgress = function(){
var pendingReqs = myHttpInterceptor.getPendingRequests();
// the request in this demo is very quick
// so I have had to change the time between checks
// you will probably want to change this for your
// own app
return $timeout(1)
.then(function(){
if(pendingReqs.hasOwnProperty(targetUrl)){
console.log('*** stil pending ***');
return $scope.askProgress();
}
console.log('*** no pending requests ***');
$timeout.cancel();
})
}
$scope.callService();
$scope.askProgress();
});
In my AngularJS application I'm trying to use finally in $http call.
This is my service
app.service("webResource", function($http, $q) {
return {
post : function(url, json) {
var defer = $q.defer();
$http.post(url, json)
.success(function (data, status) {
if (data!=null) {
defer.resolve(data);
} else {
defer.notify("Send notify....");
}
}).error(function (data, status) {
defer.reject({"response":data, "status": status});
});
return defer.promise;
}
};
});
In my controller I have
$scope.callServer = function() {
var promise = webResource.post('someurl',$scope.data);
promise
.then(
function(data) {
alert("Success");
//Do for success
},
function(data) {
alert("Error");
//Do for failure
},
function(data) {
alert("Notify");
}
).finally(function() {
alert("Finally");
});
};
If it's resolved or rejected, it's working fine. But if it's notify it alerts "Notify", but then not alerting "Finally". Why is that?
I'm using AngularJS 1.4.2 version
.finally takes two callbacks in the following form:
.finally(callback, notifyCallback)
You are currently only passing a single callback to your finally handler, as such you will only trigger the alert('Finally!) on either resolve or reject.
Add a second callback to the .finally step and you should be seeing a callback triggered when you do deferred.notify.
docs ($q.promiseAPI)
The reason it doesn't go to finally is because you can have multiple notifies. From the docs:
"notify(value) - provides updates on the status of the promise's execution. This may be called multiple times before the promise is either resolved or rejected."
Finally is once the promise is completely finished (both resolve and reject finish the promise)
https://docs.angularjs.org/api/ng/service/$q#the-deferred-api
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 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();
};
});
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).