2

I cannot get a binded service value to update when it is changed. I have tried numerous methods of doing so but none of them have worked, what am I doing wrong? From everything I have seen, this seems like it should work...

HTML:

<div class="drawer" ng-controller="DrawerController">
  {{activeCountry}}
</div>

Controller:

angular.module('worldboxApp')
    .controller('DrawerController', ['$scope', 'mapService', function($scope, mapService) {

        $scope.$watch(function() { return mapService.activeCountry }, function(newValue, oldValue) {
            $scope.activeCountry = mapService.activeCountry;
        });

    }]);

Service:

angular.module('worldboxApp').
    service('mapService', function(dbService, mapboxService, userService) {

    this.init = function() {
        this.activeCountry = {};
    }

    this.countryClick = function(e) {
        this.activeCountry = e.layer.feature;
    };

    this.init();
});

I put a break point to make sure the mapService.activeCountry variable is being changed, but all that ever shows in the html is {}.

Nate
  • 139
  • 1
  • 7

2 Answers2

1

In some case $watch is not working with factory object. Than you may use events for updates.

 app.factory('userService',['$rootScope',function($rootScope){
  var user = {};
  return {

  getFirstname : function () {
    return user.firstname;
  },

  setFirstname : function (firstname) {
    user.firstname = firstname;
    $rootScope.$broadcast("updates");
  }

}
}]);
app.controller('MainCtrl',['userService','$scope','$rootScope', function(userService,$scope,$rootScope) {
  userService.setFirstname("bharat");
  $scope.name = userService.getFirstname();
  $rootScope.$on("updates",function(){
    $scope.name = userService.getFirstname();
  });
}]);

app.controller('one',['userService','$scope', function(userService,$scope) {
  $scope.updateName=function(){
    userService.setFirstname($scope.firstname);
  }
}]);

Here is the plunker

Note:- In Some case if broadcast event is not fired instantly you may use $timeout. I have added this in plunker and time depends on your needs. this will work for both factories and services.

Bharat Bhushan
  • 2,077
  • 2
  • 21
  • 34
  • Technically this works but I am not getting the new value when the event is fired, only an empty object still. It's as if the injected mapService in the controller is a different instance of the original mapService. I thought that services were basically singletons but my results are suggesting otherwise. – Nate May 06 '15 at 04:13
  • I just fixed the issue by using your method and changing the service to a factory. I guess I did not properly understand the difference between the two. Thank you for your help. – Nate May 06 '15 at 04:25
  • Happy to help you. Good job @Nate – Bharat Bhushan May 06 '15 at 04:44
1

If you work with objects and their properties on your scope, rather than directly with strings/numbers/booleans, you're more likely to maintain references to the correct scope.

I believe the guideline is that you generally want to have a '.' (dot) in your bindings (esp for ngModel) - that is, {{data.something}} is generally better than just {{something}}. If you update a property on an object, the reference to the parent object is maintained and the updated property can be seen by Angular.

This generally doesn't matter for props you're setting and modifying only in the controller, but for values returned from a service (and that may be shared by multiple consumers of the service), I find it helps to work with an object.

See (these focus on relevance to ngModel binding):

angular.module('worldboxApp', []);

/* Controller */
angular.module('worldboxApp')
  .controller('DrawerController', ['$scope', 'mapService',
    function($scope, mapService) {
      //map to an object (by ref) rather than just a string (by val), otherwise it's easy to lose reference
      $scope.data = mapService.data;
      $scope.setCountry = setCountry; //see below

      function setCountry(country) {
        // could have just set $scope.setCountry = mapService.setCountry;
        // however we can wrap it here if we want to do something less generic
        // like getting data out of an event object, before passing it on to
        // the service.
        mapService.setCountry(country);
      }
    }
  ]);

/* Service */
angular.module('worldboxApp')
  .service('mapService', ['$log',
    function($log) {
      var self = this; //so that the functions can reference .data; 'this' within the functions would not reach the correct scope
      self.data = {
        activeCountry: null
      }; //we use an object since it can be returned by reference, and changing activeCountry's value will not break the link between it here and the controller using it

      _init();

      function _init() {
        self.data.activeCountry = '';
        $log.log('Init was called!');
      }


      this.setCountry = function _setCountry(country) {
        $log.log('setCountry was called: ' + country);
        self.data.activeCountry = country;
      }
    }
  ]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
<div ng-app="worldboxApp">
  <div ng-controller="DrawerController">
    <button ng-click="setCountry('USA')">USA</button>
    <br />
    <button ng-click="setCountry('AUS')">AUS</button>
    <br />Active Country: {{data.activeCountry}}
  </div>
</div>
Community
  • 1
  • 1
JcT
  • 3,539
  • 1
  • 24
  • 34
  • I think I understand. So if I _don't_ return the function and still try to use it, it is basically an instance of the original function, and not "static?" I assumed the entire service would act as a singleton, regardless of what I returned. – Nate May 06 '15 at 14:27
  • 1
    Hi Nate, apologies - your assumptions are not wrong, my answer was unclear and included changes/recommendations that were more stylistic than specific to resolving the issue. I've edited to try and clarify, and have implemented the service more similarly to how you were doing it. Basically I believe the key difference is returning and working with an object and its properties, which helps keep references from breaking as things can end up pointing to the wrong scopes. – JcT May 06 '15 at 15:42
  • Hmm. I understand now but I still was not able to get the data to update without using a broadcast like Bharat suggested. The combination of these two answers solved my problem however. – Nate May 08 '15 at 18:44