AngularJS : How to watch service variables?

后端 未结 21 1390
盖世英雄少女心
盖世英雄少女心 2020-11-22 09:12

I have a service, say:

factory(\'aService\', [\'$rootScope\', \'$resource\', function ($rootScope, $resource) {
  var service = {
    foo: []
  };

  return          


        
相关标签:
21条回答
  • 2020-11-22 09:25

    You can insert the service in $rootScope and watch:

    myApp.run(function($rootScope, aService){
        $rootScope.aService = aService;
        $rootScope.$watch('aService', function(){
            alert('Watch');
        }, true);
    });
    

    In your controller:

    myApp.controller('main', function($scope){
        $scope.aService.foo = 'change';
    });
    

    Other option is to use a external library like: https://github.com/melanke/Watch.JS

    Works with: IE 9+, FF 4+, SF 5+, WebKit, CH 7+, OP 12+, BESEN, Node.JS , Rhino 1.7+

    You can observe the changes of one, many or all object attributes.

    Example:

    var ex3 = {
        attr1: 0,
        attr2: "initial value of attr2",
        attr3: ["a", 3, null]
    };   
    watch(ex3, function(){
        alert("some attribute of ex3 changes!");
    });
    ex3.attr3.push("new value");​
    
    0 讨论(0)
  • 2020-11-22 09:26

    // service: (nothing special here)

    myApp.service('myService', function() {
      return { someVariable:'abc123' };
    });
    

    // ctrl:

    myApp.controller('MyCtrl', function($scope, myService) {
    
      $scope.someVariable = myService.someVariable;
    
      // watch the service and update this ctrl...
      $scope.$watch(function(){
        return myService.someVariable;
      }, function(newValue){
        $scope.someVariable = newValue;
      });
    });
    
    0 讨论(0)
  • 2020-11-22 09:26

    I've seen some terrible observer patterns here that cause memory leaks on large applications.

    I might be a little late but it's as simple as this.

    The watch function watches for reference changes (primitive types) if you want to watch something like array push simply use:

    someArray.push(someObj); someArray = someArray.splice(0);

    This will update the reference and update the watch from anywhere. Including a services getter method. Anything that's a primitive will be updated automatically.

    0 讨论(0)
  • 2020-11-22 09:28

    I have written two simple utility services that help me track service properties changes.

    If you want to skip the long explanation, you can go strait to jsfiddle

    1. WatchObj

    mod.service('WatchObj', ['$rootScope', WatchObjService]);
    
    function WatchObjService($rootScope) {
      // returns watch function
      // obj: the object to watch for
      // fields: the array of fields to watch
      // target: where to assign changes (usually it's $scope or controller instance)
      // $scope: optional, if not provided $rootScope is use
      return function watch_obj(obj, fields, target, $scope) {
        $scope = $scope || $rootScope;
        //initialize watches and create an array of "unwatch functions"
        var watched = fields.map(function(field) {
          return $scope.$watch(
            function() {
              return obj[field];
            },
            function(new_val) {
              target[field] = new_val;
            }
          );
        });
        //unregister function will unregister all our watches
        var unregister = function unregister_watch_obj() {
          watched.map(function(unregister) {
            unregister();
          });
        };
        //automatically unregister when scope is destroyed
        $scope.$on('$destroy', unregister);
        return unregister;
      };
    }

    This service is used in the controller in the following way: Suppose you have a service "testService" with the properties 'prop1', 'prop2', 'prop3'. You want to watch and assign to scope 'prop1' and 'prop2'. With the watch service it will look like that:

    app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);
    
    function TestWatchCtrl($scope, testService, watch) {
      $scope.prop1 = testService.prop1;
      $scope.prop2 = testService.prop2;
      $scope.prop3 = testService.prop3;
      watch(testService, ['prop1', 'prop2'], $scope, $scope);
    }

    1. apply Watch obj is great, but it is not enough if you have asynchronous code in your service. For that case, I use a second utility which looks like that:

    mod.service('apply', ['$timeout', ApplyService]);
    
    function ApplyService($timeout) {
      return function apply() {
        $timeout(function() {});
      };
    }

    I would trigger it in the end of my async code to trigger the $digest loop. Like that:

    app.service('TestService', ['apply', TestService]);
    
    function TestService(apply) {
      this.apply = apply;
    }
    TestService.prototype.test3 = function() {
      setTimeout(function() {
        this.prop1 = 'changed_test_2';
        this.prop2 = 'changed2_test_2';
        this.prop3 = 'changed3_test_2';
        this.apply(); //trigger $digest loop
      }.bind(this));
    }

    So, all of that together will look like that (you can run it or open fiddle):

    // TEST app code
    
    var app = angular.module('app', ['watch_utils']);
    
    app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);
    
    function TestWatchCtrl($scope, testService, watch) {
      $scope.prop1 = testService.prop1;
      $scope.prop2 = testService.prop2;
      $scope.prop3 = testService.prop3;
      watch(testService, ['prop1', 'prop2'], $scope, $scope);
      $scope.test1 = function() {
        testService.test1();
      };
      $scope.test2 = function() {
        testService.test2();
      };
      $scope.test3 = function() {
        testService.test3();
      };
    }
    
    app.service('TestService', ['apply', TestService]);
    
    function TestService(apply) {
      this.apply = apply;
      this.reset();
    }
    TestService.prototype.reset = function() {
      this.prop1 = 'unchenged';
      this.prop2 = 'unchenged2';
      this.prop3 = 'unchenged3';
    }
    TestService.prototype.test1 = function() {
      this.prop1 = 'changed_test_1';
      this.prop2 = 'changed2_test_1';
      this.prop3 = 'changed3_test_1';
    }
    TestService.prototype.test2 = function() {
      setTimeout(function() {
        this.prop1 = 'changed_test_2';
        this.prop2 = 'changed2_test_2';
        this.prop3 = 'changed3_test_2';
      }.bind(this));
    }
    TestService.prototype.test3 = function() {
      setTimeout(function() {
        this.prop1 = 'changed_test_2';
        this.prop2 = 'changed2_test_2';
        this.prop3 = 'changed3_test_2';
        this.apply();
      }.bind(this));
    }
    //END TEST APP CODE
    
    //WATCH UTILS
    var mod = angular.module('watch_utils', []);
    
    mod.service('apply', ['$timeout', ApplyService]);
    
    function ApplyService($timeout) {
      return function apply() {
        $timeout(function() {});
      };
    }
    
    mod.service('WatchObj', ['$rootScope', WatchObjService]);
    
    function WatchObjService($rootScope) {
      // target not always equals $scope, for example when using bindToController syntax in 
      //directives
      return function watch_obj(obj, fields, target, $scope) {
        // if $scope is not provided, $rootScope is used
        $scope = $scope || $rootScope;
        var watched = fields.map(function(field) {
          return $scope.$watch(
            function() {
              return obj[field];
            },
            function(new_val) {
              target[field] = new_val;
            }
          );
        });
        var unregister = function unregister_watch_obj() {
          watched.map(function(unregister) {
            unregister();
          });
        };
        $scope.$on('$destroy', unregister);
        return unregister;
      };
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    <div class='test' ng-app="app" ng-controller="TestWatch">
      prop1: {{prop1}}
      <br>prop2: {{prop2}}
      <br>prop3 (unwatched): {{prop3}}
      <br>
      <button ng-click="test1()">
        Simple props change
      </button>
      <button ng-click="test2()">
        Async props change
      </button>
      <button ng-click="test3()">
        Async props change with apply
      </button>
    </div>

    0 讨论(0)
  • 2020-11-22 09:29

    Here's my generic approach.

    mainApp.service('aService',[function(){
            var self = this;
            var callbacks = {};
    
            this.foo = '';
    
            this.watch = function(variable, callback) {
                if (typeof(self[variable]) !== 'undefined') {
                    if (!callbacks[variable]) {
                        callbacks[variable] = [];
                    }
                    callbacks[variable].push(callback);
                }
            }
    
            this.notifyWatchersOn = function(variable) {
                if (!self[variable]) return;
                if (!callbacks[variable]) return;
    
                angular.forEach(callbacks[variable], function(callback, key){
                    callback(self[variable]);
                });
            }
    
            this.changeFoo = function(newValue) {
                self.foo = newValue;
                self.notifyWatchersOn('foo');
            }
    
        }]);
    

    In Your Controller

    function FooCtrl($scope, aService) {
        $scope.foo;
    
        $scope._initWatchers = function() {
            aService.watch('foo', $scope._onFooChange);
        }
    
        $scope._onFooChange = function(newValue) {
            $scope.foo = newValue;
        }
    
        $scope._initWatchers();
    
    }
    
    FooCtrl.$inject = ['$scope', 'aService'];
    
    0 讨论(0)
  • 2020-11-22 09:29

    while facing a very similar issue I watched a function in scope and had the function return the service variable. I have created a js fiddle. you can find the code below.

        var myApp = angular.module("myApp",[]);
    
    myApp.factory("randomService", function($timeout){
        var retValue = {};
        var data = 0;
    
        retValue.startService = function(){
            updateData();
        }
    
        retValue.getData = function(){
            return data;
        }
    
        function updateData(){
            $timeout(function(){
                data = Math.floor(Math.random() * 100);
                updateData()
            }, 500);
        }
    
        return retValue;
    });
    
    myApp.controller("myController", function($scope, randomService){
        $scope.data = 0;
        $scope.dataUpdated = 0;
        $scope.watchCalled = 0;
        randomService.startService();
    
        $scope.getRandomData = function(){
            return randomService.getData();    
        }
    
        $scope.$watch("getRandomData()", function(newValue, oldValue){
            if(oldValue != newValue){
                $scope.data = newValue;
                $scope.dataUpdated++;
            }
                $scope.watchCalled++;
        });
    });
    
    0 讨论(0)
提交回复
热议问题