0

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?

isherwood
  • 58,414
  • 16
  • 114
  • 157
Mark Olbert
  • 6,584
  • 9
  • 35
  • 69
  • Your comment says the force update does nothing since the data is the same, did you inspect `$scope.dataContext.pins` in the `visitUpdate` listener to confirm it was updated? You might have to pass the updated data along with the event. – Tony May 14 '15 at 17:18
  • Good question, Tony. In fact, I did check the values in the listener both before and after the assignment. They were the same, from which I conclude that the update in the service had taken effect. But I was thinking I'd try simply passing through the changes in the event and let the map controller handle the updates. Maybe it's some kind of oddball scoping problem. – Mark Olbert May 14 '15 at 19:03
  • What I think _might_ be happening is the actual updates get lost as they are set in a promise callback, which may or may not be in angular scope. If you can't find a solution, I'd recommend trying to recreate the issue in its simplest form and/or create a plnkr. As it stands, it's really hard for others to test the code as is. – Tony May 14 '15 at 19:07

0 Answers0