421

我有一个服务,说:

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

  return service;
}]);

我想foo用来控制一个以 HTML 呈现的列表:

<div ng-controller="FooCtrl">
  <div ng-repeat="item in foo">{{ item }}</div>
</div>

为了让控制器检测何时aService.foo更新,我拼凑了这个模式,我将 aService 添加到控制器$scope,然后使用$scope.$watch()

function FooCtrl($scope, aService) {                                                                                                                              
  $scope.aService = aService;
  $scope.foo = aService.foo;

  $scope.$watch('aService.foo', function (newVal, oldVal, scope) {
    if(newVal) { 
      scope.foo = newVal;
    }
  });
}

这感觉很牵强,我一直在每个使用服务变量的控制器中重复它。有没有更好的方法来完成观察共享变量?

4

21 回答 21

280

如果你想避免$watch.

在服务中:

factory('aService', function() {
  var observerCallbacks = [];

  //register an observer
  this.registerObserverCallback = function(callback){
    observerCallbacks.push(callback);
  };

  //call this when you know 'foo' has been changed
  var notifyObservers = function(){
    angular.forEach(observerCallbacks, function(callback){
      callback();
    });
  };

  //example of when you may want to notify observers
  this.foo = someNgResource.query().$then(function(){
    notifyObservers();
  });
});

在控制器中:

function FooCtrl($scope, aService){
  var updateFoo = function(){
    $scope.foo = aService.foo;
  };

  aService.registerObserverCallback(updateFoo);
  //service now in control of updating foo
};
于 2013-07-09T21:49:06.003 回答
232

在这样的场景中,多个/未知对象可能对更改感兴趣,$rootScope.$broadcast请从正在更改的项目中使用。

而不是创建自己的监听器注册表(必须在各种 $destroy 上清理),您应该能够$broadcast从有问题的服务中获取。

您仍然必须$on在每个侦听器中编写处理程序,但该模式与多次调用分离$digest,从而避免了长时间运行的观察程序的风险。

这样,侦听器也可以从DOM和/或不同的子范围进出,而服务不会改变其行为。

** 更新:示例 **

广播在“全球”服务中最有意义,它可能会影响您应用程序中的无数其他事情。一个很好的例子是用户服务,其中可能发生许多事件,例如登录、注销、更新、空闲等。我相信这是广播最有意义的地方,因为任何范围都可以侦听事件,而无需甚至注入服务,它不需要评估任何表达式或缓存结果来检查更改。它只是触发并忘记(所以请确保它是触发后忘记通知,而不是需要采取行动的东西)

.factory('UserService', [ '$rootScope', function($rootScope) {
   var service = <whatever you do for the object>

   service.save = function(data) {
     .. validate data and update model ..
     // notify listeners and provide the data that changed [optional]
     $rootScope.$broadcast('user:updated',data);
   }

   // alternatively, create a callback function and $broadcast from there if making an ajax call

   return service;
}]);

当 save() 函数完成并且数据有效时,上面的服务将向每个范围广播一条消息。或者,如果它是 $resource 或 ajax 提交,请将广播调用移动到回调中,以便在服务器响应时触发。广播特别适合这种模式,因为每个侦听器只是等待事件,而不需要检查每个 $digest 的范围。监听器看起来像:

.controller('UserCtrl', [ 'UserService', '$scope', function(UserService, $scope) {

  var user = UserService.getUser();

  // if you don't want to expose the actual object in your scope you could expose just the values, or derive a value for your purposes
   $scope.name = user.firstname + ' ' +user.lastname;

   $scope.$on('user:updated', function(event,data) {
     // you could inspect the data to see if what you care about changed, or just update your own scope
     $scope.name = user.firstname + ' ' + user.lastname;
   });

   // different event names let you group your code and logic by what happened
   $scope.$on('user:logout', function(event,data) {
     .. do something differently entirely ..
   });

 }]);

这样做的好处之一是消除了多个手表。如果您像上面的示例那样组合字段或派生值,则必须同时查看 firstname 和 lastname 属性。观察 getUser() 函数只有在用户对象在更新时被替换时才会起作用,如果用户对象只是更新了它的属性,它就不会触发。在这种情况下,您必须进行深入观察,而且会更加密集。

$broadcast 将消息从它被调用的范围发送到任何子范围。因此,从 $rootScope 调用它会在每个范围内触发。例如,如果您要从控制器的作用域进行 $broadcast,它只会在从控制器作用域继承的作用域中触发。$emit 则相反,其行为类似于 DOM 事件,因为它在作用域链上冒泡。

请记住,在某些情况下 $broadcast 很有意义,并且在某些情况下 $watch 是更好的选择 - 特别是在具有非常具体的 watch 表达式的隔离范围内。

于 2013-12-31T21:55:56.853 回答
49

我使用与@dtheodot 类似的方法,但使用角度承诺而不是传递回调

app.service('myService', function($q) {
    var self = this,
        defer = $q.defer();

    this.foo = 0;

    this.observeFoo = function() {
        return defer.promise;
    }

    this.setFoo = function(foo) {
        self.foo = foo;
        defer.notify(self.foo);
    }
})

然后在任何地方使用myService.setFoo(foo)方法来更新foo服务。在您的控制器中,您可以将其用作:

myService.observeFoo().then(null, null, function(foo){
    $scope.foo = foo;
})

的前两个参数then是成功和错误回调,第三个是通知回调。

$q 的参考。

于 2014-04-11T10:55:57.793 回答
43

没有手表或观察者回调(http://jsfiddle.net/zymotik/853wvv7s/):

JavaScript:

angular.module("Demo", [])
    .factory("DemoService", function($timeout) {

        function DemoService() {
            var self = this;
            self.name = "Demo Service";

            self.count = 0;

            self.counter = function(){
                self.count++;
                $timeout(self.counter, 1000);
            }

            self.addOneHundred = function(){
                self.count+=100;
            }

            self.counter();
        }

        return new DemoService();

    })
    .controller("DemoController", function($scope, DemoService) {

        $scope.service = DemoService;

        $scope.minusOneHundred = function() {
            DemoService.count -= 100;
        }

    });

HTML

<div ng-app="Demo" ng-controller="DemoController">
    <div>
        <h4>{{service.name}}</h4>
        <p>Count: {{service.count}}</p>
    </div>
</div>

当我们从服务传回一个对象而不是一个值时,这个 JavaScript 就起作用了。当从服务返回一个 JavaScript 对象时,Angular 会将监视添加到它的所有属性中。

另请注意,我使用 'var self = this' 因为我需要在 $timeout 执行时保留对原始对象的引用,否则 'this' 将引用窗口对象。

于 2015-03-05T08:59:05.050 回答
29

我偶然发现了这个问题,正在寻找类似的东西,但我认为它值得对正在发生的事情进行彻底的解释,以及一些额外的解决方案。

当一个 Angular 表达式(例如您使用的那个)出现在 HTML 中时,Angular 会自动设置一个$watchfor $scope.foo,并在发生更改时更新 HTML $scope.foo

<div ng-controller="FooCtrl">
  <div ng-repeat="item in foo">{{ item }}</div>
</div>

这里未说明的问题是,有两件事之一正在影响aService.foo ,因此未检测到更改。这两种可能性是:

  1. aService.foo每次都设置为一个新数组,导致对它的引用已过时。
  2. aService.foo正在以更新时$digest不会触发循环的方式进行更新。

问题 1:过时的引用

考虑第一种可能性,假设$digest正在应用 a,如果aService.foo始终是同一个数组,则自动设置$watch将检测到更改,如下面的代码片段所示。

解决方案 1-a:确保数组或对象在每次更新时都是同一个对象

angular.module('myApp', [])
  .factory('aService', [
    '$interval',
    function($interval) {
      var service = {
        foo: []
      };

      // Create a new array on each update, appending the previous items and 
      // adding one new item each time
      $interval(function() {
        if (service.foo.length < 10) {
          var newArray = []
          Array.prototype.push.apply(newArray, service.foo);
          newArray.push(Math.random());
          service.foo = newArray;
        }
      }, 1000);

      return service;
    }
  ])
  .factory('aService2', [
    '$interval',
    function($interval) {
      var service = {
        foo: []
      };

      // Keep the same array, just add new items on each update
      $interval(function() {
        if (service.foo.length < 10) {
          service.foo.push(Math.random());
        }
      }, 1000);

      return service;
    }
  ])
  .controller('FooCtrl', [
    '$scope',
    'aService',
    'aService2',
    function FooCtrl($scope, aService, aService2) {
      $scope.foo = aService.foo;
      $scope.foo2 = aService2.foo;
    }
  ]);
<!DOCTYPE html>
<html>

<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

<body ng-app="myApp">
  <div ng-controller="FooCtrl">
    <h1>Array changes on each update</h1>
    <div ng-repeat="item in foo">{{ item }}</div>
    <h1>Array is the same on each udpate</h1>
    <div ng-repeat="item in foo2">{{ item }}</div>
  </div>
</body>

</html>

如您所见,据说附加到的 ng-repeat在更改aService.foo时不会更新aService.foo,但附加到的 ng-repeataService2.foo 更新。这是因为我们的引用aService.foo已经过时,但我们的引用aService2.foo没有。我们创建了对初始数组的引用,$scope.foo = aService.foo;然后在下一次更新时被服务丢弃,这意味着$scope.foo不再引用我们想要的数组。

然而,虽然有多种方法可以确保初始引用保持完整,但有时可能需要更改对象或数组。或者,如果服务属性引用像 aString或之类的原语Number怎么办?在这些情况下,我们不能简单地依赖参考。那么我们做些什么呢?

之前给出的几个答案已经为该问题提供了一些解决方案。不过,我个人更赞成在评论中使用Jinthetallweeks建议的简单方法:

只需在 html 标记中引用 aService.foo

解决方案 1-b:将服务附加到范围,并{service}.{property}在 HTML 中引用。

意思是,只需这样做:

HTML:

<div ng-controller="FooCtrl">
  <div ng-repeat="item in aService.foo">{{ item }}</div>
</div>

JS:

function FooCtrl($scope, aService) {
    $scope.aService = aService;
}

angular.module('myApp', [])
  .factory('aService', [
    '$interval',
    function($interval) {
      var service = {
        foo: []
      };

      // Create a new array on each update, appending the previous items and 
      // adding one new item each time
      $interval(function() {
        if (service.foo.length < 10) {
          var newArray = []
          Array.prototype.push.apply(newArray, service.foo);
          newArray.push(Math.random());
          service.foo = newArray;
        }
      }, 1000);

      return service;
    }
  ])
  .controller('FooCtrl', [
    '$scope',
    'aService',
    function FooCtrl($scope, aService) {
      $scope.aService = aService;
    }
  ]);
<!DOCTYPE html>
<html>

<head>
  <script data-require="angular.js@1.4.7" data-semver="1.4.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

<body ng-app="myApp">
  <div ng-controller="FooCtrl">
    <h1>Array changes on each update</h1>
    <div ng-repeat="item in aService.foo">{{ item }}</div>
  </div>
</body>

</html>

这样,将在 each 上$watch解析,这将获得正确更新的值。aService.foo$digest

这就是你试图用你的解决方法做的事情,但方式要少得多。$watch您在控制器中添加了一个不必要的,它foo$scope更改时显式放置。$watch当您附加aService而不是附加aService.foo到, 并在标记中$scope显式绑定到时,您不需要额外的东西。aService.foo


现在假设$digest正在应用一个循环,这一切都很好。在上面的示例中,我使用 Angular 的$interval服务来更新数组,每次更新后它都会自动启动一个$digest循环。但是,如果服务变量(无论出于何种原因)没有在“Angular 世界”中得到更新怎么办。换句话说,当服务属性发生变化时,我们不会自动激活一个循环$digest


问题2:缺失$digest

这里的许多解决方案都可以解决这个问题,但我同意Code Whisperer

我们使用像 Angular 这样的框架的原因是为了不编写我们自己的观察者模式

因此,我更愿意继续使用aService.fooHTML 标记中的引用,如上面第二个示例所示,而不必在 Controller 中注册额外的回调。

解决方案 2:使用 setter 和 getter$rootScope.$apply()

我很惊讶没有人建议使用settergetter。此功能是在 ECMAScript5 中引入的,因此已经存在多年。当然,这意味着无论出于何种原因,如果您需要支持非常旧的浏览器,那么这种方法将不起作用,但我觉得 getter 和 setter 在 JavaScript 中的使用严重不足。在这种特殊情况下,它们可能非常有用:

factory('aService', [
  '$rootScope',
  function($rootScope) {
    var realFoo = [];

    var service = {
      set foo(a) {
        realFoo = a;
        $rootScope.$apply();
      },
      get foo() {
        return realFoo;
      }
    };
  // ...
}

angular.module('myApp', [])
  .factory('aService', [
    '$rootScope',
    function($rootScope) {
      var realFoo = [];

      var service = {
        set foo(a) {
          realFoo = a;
          $rootScope.$apply();
        },
        get foo() {
          return realFoo;
        }
      };

      // Create a new array on each update, appending the previous items and 
      // adding one new item each time
      setInterval(function() {
        if (service.foo.length < 10) {
          var newArray = [];
          Array.prototype.push.apply(newArray, service.foo);
          newArray.push(Math.random());
          service.foo = newArray;
        }
      }, 1000);

      return service;
    }
  ])
  .controller('FooCtrl', [
    '$scope',
    'aService',
    function FooCtrl($scope, aService) {
      $scope.aService = aService;
    }
  ]);
<!DOCTYPE html>
<html>

<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

<body ng-app="myApp">
  <div ng-controller="FooCtrl">
    <h1>Using a Getter/Setter</h1>
    <div ng-repeat="item in aService.foo">{{ item }}</div>
  </div>
</body>

</html>

在这里,我在服务函数中添加了一个“私有”变量:realFoo. get foo()分别使用对象上的和set foo()函数更新和检索此 get service

注意$rootScope.$apply()set 函数中的使用。这确保了 Angular 将知道对service.foo. 如果您收到“inprog”错误,请参阅这个有用的参考页面,或者如果您使用 Angular >= 1.3,您可以只使用$rootScope.$applyAsync().

aService.foo如果更新非常频繁,也要注意这一点,因为这可能会显着影响性能。如果性能是一个问题,您可以使用 setter 设置类似于此处其他答案的观察者模式。

于 2015-11-11T04:52:02.990 回答
28

据我所知,您不必做那么精细的事情。您已经将服务中的 foo 分配给了您的范围,并且因为 foo 是一个数组(反过来,它是一个通过引用分配的对象!)。所以,你需要做的就是这样的:

function FooCtrl($scope, aService) {                                                                                                                              
  $scope.foo = aService.foo;

 }

如果同一个 Ctrl 中的一些其他变量依赖于 foo 的变化,那么是的,您需要一个手表来观察 foo 并对该变量进行更改。但只要是简单的参考,观看是没有必要的。希望这可以帮助。

于 2012-09-25T08:11:57.227 回答
9

您可以在 $rootScope 中插入服务并观看:

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

在您的控制器中:

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

其他选择是使用外部库,例如:https ://github.com/melanke/Watch.JS

适用于:IE 9+、FF 4+、SF 5+、WebKit、CH 7+、OP 12+、BESEN、Node.JS、Rhino 1.7+

您可以观察到一个、多个或所有对象属性的变化。

例子:

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");​
于 2014-09-13T15:24:41.773 回答
6

您可以在工厂内部观看更改,然后广播更改

angular.module('MyApp').factory('aFactory', function ($rootScope) {
    // Define your factory content
    var result = {
        'key': value
    };

    // add a listener on a key        
    $rootScope.$watch(function () {
        return result.key;
    }, function (newValue, oldValue, scope) {
        // This is called after the key "key" has changed, a good idea is to broadcast a message that key has changed
        $rootScope.$broadcast('aFactory:keyChanged', newValue);
    }, true);

    return result;
});

然后在你的控制器中:

angular.module('MyApp').controller('aController', ['$rootScope', function ($rootScope) {

    $rootScope.$on('aFactory:keyChanged', function currentCityChanged(event, value) {
        // do something
    });
}]);

通过这种方式,您将所有相关的工厂代码放在其描述中,然后您只能依赖来自外部的广播

于 2014-11-24T05:54:45.633 回答
6

==更新==

现在在 $watch 中非常简单。

笔在这里

HTML:

<div class="container" data-ng-app="app">

  <div class="well" data-ng-controller="FooCtrl">
    <p><strong>FooController</strong></p>
    <div class="row">
      <div class="col-sm-6">
        <p><a href="" ng-click="setItems([ { name: 'I am single item' } ])">Send one item</a></p>
        <p><a href="" ng-click="setItems([ { name: 'Item 1 of 2' }, { name: 'Item 2 of 2' } ])">Send two items</a></p>
        <p><a href="" ng-click="setItems([ { name: 'Item 1 of 3' }, { name: 'Item 2 of 3' }, { name: 'Item 3 of 3' } ])">Send three items</a></p>
      </div>
      <div class="col-sm-6">
        <p><a href="" ng-click="setName('Sheldon')">Send name: Sheldon</a></p>
        <p><a href="" ng-click="setName('Leonard')">Send name: Leonard</a></p>
        <p><a href="" ng-click="setName('Penny')">Send name: Penny</a></p>
      </div>
    </div>
  </div>

  <div class="well" data-ng-controller="BarCtrl">
    <p><strong>BarController</strong></p>
    <p ng-if="name">Name is: {{ name }}</p>
    <div ng-repeat="item in items">{{ item.name }}</div>
  </div>

</div>

JavaScript:

var app = angular.module('app', []);

app.factory('PostmanService', function() {
  var Postman = {};
  Postman.set = function(key, val) {
    Postman[key] = val;
  };
  Postman.get = function(key) {
    return Postman[key];
  };
  Postman.watch = function($scope, key, onChange) {
    return $scope.$watch(
      // This function returns the value being watched. It is called for each turn of the $digest loop
      function() {
        return Postman.get(key);
      },
      // This is the change listener, called when the value returned from the above function changes
      function(newValue, oldValue) {
        if (newValue !== oldValue) {
          // Only update if the value changed
          $scope[key] = newValue;
          // Run onChange if it is function
          if (angular.isFunction(onChange)) {
            onChange(newValue, oldValue);
          }
        }
      }
    );
  };
  return Postman;
});

app.controller('FooCtrl', ['$scope', 'PostmanService', function($scope, PostmanService) {
  $scope.setItems = function(items) {
    PostmanService.set('items', items);
  };
  $scope.setName = function(name) {
    PostmanService.set('name', name);
  };
}]);

app.controller('BarCtrl', ['$scope', 'PostmanService', function($scope, PostmanService) {
  $scope.items = [];
  $scope.name = '';
  PostmanService.watch($scope, 'items');
  PostmanService.watch($scope, 'name', function(newVal, oldVal) {
    alert('Hi, ' + newVal + '!');
  });
}]);
于 2015-10-23T09:33:15.203 回答
4

dtheodor 的回答的基础上,您可以使用类似于下面的内容来确保您不会忘记取消注册回调......尽管有些人可能会反对将其传递$scope给服务。

factory('aService', function() {
  var observerCallbacks = [];

  /**
   * Registers a function that will be called when
   * any modifications are made.
   *
   * For convenience the callback is called immediately after registering
   * which can be prevented with `preventImmediate` param.
   *
   * Will also automatically unregister the callback upon scope destory.
   */
  this.registerObserver = function($scope, cb, preventImmediate){
    observerCallbacks.push(cb);

    if (preventImmediate !== true) {
      cb();
    }

    $scope.$on('$destroy', function () {
      observerCallbacks.remove(cb);
    });
  };

  function notifyObservers() {
    observerCallbacks.forEach(function (cb) {
      cb();
    });
  };

  this.foo = someNgResource.query().$then(function(){
    notifyObservers();
  });
});

Array.remove 是一个扩展方法,如下所示:

/**
 * Removes the given item the current array.
 *
 * @param  {Object}  item   The item to remove.
 * @return {Boolean}        True if the item is removed.
 */
Array.prototype.remove = function (item /*, thisp */) {
    var idx = this.indexOf(item);

    if (idx > -1) {
        this.splice(idx, 1);

        return true;
    }
    return false;
};
于 2013-09-02T09:03:58.203 回答
2

对于像我这样只是在寻找简单解决方案的人来说,这几乎完全符合您在控制器中使用普通 $watch 的期望。唯一的区别是,它在它的 javascript 上下文中而不是在特定范围内评估字符串。您必须将 $rootScope 注入您的服务,尽管它仅用于正确连接到摘要周期。

function watch(target, callback, deep) {
    $rootScope.$watch(function () {return eval(target);}, callback, deep);
};
于 2014-09-29T09:26:18.547 回答
2

我遇到了这个问题,但事实证明我的问题是我应该使用 angular $interval 提供程序时使用了 setInterval。这也是 setTimeout 的情况(使用 $timeout 代替)。我知道这不是 OP 问题的答案,但它可能对一些人有所帮助,因为它帮助了我。

于 2015-02-18T01:02:36.307 回答
2

在面临一个非常相似的问题时,我在范围内观察了一个函数并让该函数返回服务变量。我创建了一个js fiddle。您可以在下面找到代码。

    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++;
    });
});
于 2014-12-29T08:01:43.510 回答
2

这是我的通用方法。

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');
        }

    }]);

在你的控制器中

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'];
于 2013-09-17T03:44:30.953 回答
2

我在另一个有类似问题但方法完全不同的线程上找到了一个非常好的解决方案。资料来源:AngularJS:当 $rootScope 值更改时,指令内的 $watch 不起作用

基本上那里的解决方案告诉不要使用$watch,因为它是非常重的解决方案。相反,他们建议使用$emitand $on

我的问题是在我的服务中观察一个变量并在指令中做出反应。而且用上面的方法就很简单了!

我的模块/服务示例:

angular.module('xxx').factory('example', function ($rootScope) {
    var user;

    return {
        setUser: function (aUser) {
            user = aUser;
            $rootScope.$emit('user:change');
        },
        getUser: function () {
            return (user) ? user : false;
        },
        ...
    };
});

所以基本上我我的user - 每当它设置为新值时,我就是$emit一个user:change状态。

现在就我而言,在我使用的指令中:

angular.module('xxx').directive('directive', function (Auth, $rootScope) {
    return {
        ...
        link: function (scope, element, attrs) {
            ...
            $rootScope.$on('user:change', update);
        }
    };
});

现在在指令中,我听取了$rootScope定的变化——我分别做出反应。非常轻松优雅!

于 2015-07-23T12:42:52.107 回答
1

有点难看,但我已经在我的服务中添加了范围变量的注册以进行切换:

myApp.service('myService', function() {
    var self = this;
    self.value = false;
    self.c2 = function(){};
    self.callback = function(){
        self.value = !self.value; 
       self.c2();
    };

    self.on = function(){
        return self.value;
    };

    self.register = function(obj, key){ 
        self.c2 = function(){
            obj[key] = self.value; 
            obj.$apply();
        } 
    };

    return this;
});

然后在控制器中:

function MyCtrl($scope, myService) {
    $scope.name = 'Superhero';
    $scope.myVar = false;
    myService.register($scope, 'myVar');
}
于 2013-06-06T14:34:04.690 回答
1

看看这个 plunker:: 这是我能想到的最简单的例子

http://jsfiddle.net/HEdJF/

<div ng-app="myApp">
    <div ng-controller="FirstCtrl">
        <input type="text" ng-model="Data.FirstName"><!-- Input entered here -->
        <br>Input is : <strong>{{Data.FirstName}}</strong><!-- Successfully updates here -->
    </div>
    <hr>
    <div ng-controller="SecondCtrl">
        Input should also be here: {{Data.FirstName}}<!-- How do I automatically updated it here? -->
    </div>
</div>



// declare the app with no dependencies
var myApp = angular.module('myApp', []);
myApp.factory('Data', function(){
   return { FirstName: '' };
});

myApp.controller('FirstCtrl', function( $scope, Data ){
    $scope.Data = Data;
});

myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.Data = Data;
});
于 2016-09-07T11:44:37.557 回答
1

// 服务:(这里没什么特别的)

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

// 控制:

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;
  });
});
于 2016-05-13T18:54:11.330 回答
0

我在这里看到了一些可怕的观察者模式,它们会导致大型应用程序出现内存泄漏。

我可能晚了一点,但就这么简单。

如果您想查看类似数组推送的内容,watch 函数会查看引用更改(原始类型),只需使用:

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

这将更新参考并从任何地方更新手表。包括一个服务获取方法。任何原始的东西都会自动更新。

于 2015-12-26T12:35:25.093 回答
0

我迟到了,但我找到了比上面发布的答案更好的方法。我没有分配一个变量来保存服务变量的值,而是创建了一个附加到范围的函数,它返回服务变量。

控制器

$scope.foo = function(){
 return aService.foo;
}

我认为这会做你想要的。我的控制器通过这个实现不断检查我的服务的价值。老实说,这比选择的答案要简单得多。

于 2016-08-18T20:00:13.657 回答
0

我编写了两个简单的实用程序服务来帮助我跟踪服务属性的变化。

如果你想跳过冗长的解释,你可以去jsfiddle

  1. 观察对象

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;
  };
}

该服务在控制器中的使用方式如下:假设您有一个服务“testService”,其属性为“prop1”、“prop2”、“prop3”。您想观察并分配给范围“prop1”和“prop2”。使用 watch 服务,它看起来像这样:

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 很棒,但如果您的服务中有异步代码,这还不够。对于这种情况,我使用第二个实用程序,如下所示:

mod.service('apply', ['$timeout', ApplyService]);

function ApplyService($timeout) {
  return function apply() {
    $timeout(function() {});
  };
}

我会在我的异步代码末尾触发它来触发 $digest 循环。像那样:

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));
}

所以,所有这些看起来像这样(你可以运行它或打开 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>

于 2016-08-25T10:51:50.017 回答