我有一个服务,说:

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。我已经拼凑了这个模式,其中我添加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;
    }
  });
}

这感觉有点冗长,我一直在每个使用服务变量的控制器中重复这一点。有没有更好的方法来监视共享变量?


当前回答

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

而不是创建自己的侦听器注册表(必须在各种$ destroyed上进行清理),您应该能够从相关服务进行$broadcast。

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

通过这种方式,侦听器也可以从DOM和/或不同的子作用域进出,而不需要服务更改其行为。

**更新:示例**

Broadcasts would make the most sense in "global" services that could impact countless other things in your app. A good example is a User service where there are a number of events that could take place such as login, logout, update, idle, etc. I believe this is where broadcasts make the most sense because any scope can listen for an event, without even injecting the service, and it doesn't need to evaluate any expressions or cache results to inspect for changes. It just fires and forgets (so make sure it's a fire-and-forget notification, not something that requires action)

.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提交,则将广播调用移动到回调中,以便在服务器响应时触发它。广播特别适合这种模式,因为每个侦听器只需要等待事件,而不需要检查每个$摘要上的作用域。监听器看起来是这样的:

.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表达式的孤立作用域中。

其他回答

对于那些像我一样只是寻找一个简单的解决方案,这几乎完全是你所期望的在控制器中使用正常的$watch。 唯一的区别是,它在javascript上下文中计算字符串,而不是在特定的作用域上。您必须将$rootScope注入到您的服务中,尽管它只用于正确地钩子到摘要周期中。

function watch(target, callback, deep) {
    $rootScope.$watch(function () {return eval(target);}, callback, deep);
};

我使用类似的方法@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的参考。

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

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

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

我提出了这个问题,但事实证明,我的问题是,当我应该使用angular的$interval提供程序时,我却使用了setInterval。setTimeout也是如此(使用$timeout代替)。我知道这不是OP问题的答案,但它可能会有所帮助,就像它帮助了我一样。

你可以在$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 +, f4 +,稳定基金5+

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

例子:

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");​