I have a service which defines a background $interval. It watches local storage to see if some server updates didn't get made due to connectivity problems. When it finds some, it periodically tries to contact the server with the updates, deleting them from local storage when it succeeds.
Upon success, the callback also updates some properties of an angular view/model. Those changes should cause the UI to update, and elsewhere in the code of various controllers they do.
But within the $interval callback in that background service the changes do not cause the UI to update. I thought it might be some kind of failure to $apply, so I added a $rootScope.$apply() call. But that caused an error because a $digest was already in progress.
That tells me $digest is running after the callback executes -- which is what I would've expected -- but it's not causing the UI to update.
I then did a $rootScope.$emit() within the service, after the view/model was updated, and listened for the event within the controller where the UI should've updated. Within the listener I can see that the view/model the controller is based on was successfully updated by the service.
Here is the service:
app.factory('bkgndUpdates', function($interval, $q, $rootScope, dataContext, localStorage) {
var _interval = null;
var _service = {
get isRunning() { return _interval != null },
};
_service.start = function() {
if( _interval != null ) return;
_interval = $interval(function() {
// check to ensure local storage is actually available
// and has visits to process
var visits = localStorage.visits;
if( !visits || (visits.length == 0) ) return;
var recorded = [];
var promises = [];
var offline = false;
for( var idx = 0; idx < visits.length; idx++ )
{
var visit = visits[idx];
promises.push(dataContext.doVisitAction("/Walk/RecordVisit", visit)
.then(
function(resp) {
offline &= false;
var home = dataContext.findHomeByID(visit.addressID);
if( home != null ) {
// these values should cause a map icon to switch from yellow to blue
// elsewhere in the app that's what happens...but not here.
home.visitInfo.isDirty = false;
home.visitInfo.inDatabase = true;
home.visitInfo.canDelete = true;
home.visitInfo.followUpID = resp.FollowUpID;
}
recorded.push(visit.addressID);
},
function(resp) {
// something went wrong; do nothing, as the item is already
// in local storage
offline |= true;
})
);
}
$q.all(promises).then(
function(resp) {
for( var idx = 0; idx < recorded.length; idx++ )
{
localStorage.removeVisitByID(recorded[idx]);
}
if( !localStorage.hasVisits ) {
$interval.cancel(_interval);
_interval = null;
}
$rootScope.$emit("visitUpdate");
},
function(resp) {
alert("some promise failed");
});
}, 30000);
}
_service.stop = function() {
$interval.cancel(_interval);
_interval = null;
}
return _service;
});
Here's the controller for the map:
app.controller("mapCtrl", function ($scope, $rootScope, $location, dataContext) {
$scope.dataContext = dataContext;
$scope.mapInfo = {
center:dataContext.center,
zoom: dataContext.zoom,
pins: dataContext.pins,
};
$scope.$on('mapInitialized', function(evt, map){
$scope.dragend = function() {
$scope.dataContext.center = $scope.map.getCenter();
$scope.dataContext.getMapData($scope.map.getBounds());
}
$scope.zoomchanged = function() {
$scope.dataContext.zoom = $scope.map.getZoom();
$scope.dataContext.getMapData($scope.map.getBounds());
}
$scope.dataContext.getMapData($scope.map.getBounds())
.then(
function() {
$scope.mapInfo.pins = $scope.dataContext.pins;
},
function() {});
});
$scope.click = function() {
dataContext.pinIndex = this.pinindex;
if( dataContext.activePin.homes.length == 1)
{
dataContext.homeIndex = 0;
$location.path("/home");
}
else $location.path("/unit");
};
$rootScope.$on("visitUpdate", function(event) {
// I added this next line to force an update...even though the
// data on both sides of the assignment is the same (i.e., it was
// already changed
$scope.mapInfo.pins = $scope.dataContext.pins;
event.stopPropagation();
});
});
Here's the (partial) template (which relies on ngMap):
<div id="mapframe" class="google-maps">
<map name="theMap" center="{{mapInfo.center.toUrlValue()}}" zoom="{{mapInfo.zoom}}" on-dragend="dragend()" on-zoom_changed="zoomchanged()">
<marker ng-repeat="pin in mapInfo.pins" position="{{pin.latitude}}, {{pin.longitude}}" title="{{pin.streetAddress}}" pinindex="{{$index}}" on-click="click()"
icon="{{pin.icon}}"></marker>
</map>
</div>
So why isn't the UI updating?