我有一个服务,说:

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

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


当前回答

当我面对一个非常相似的问题时,我观察了一个作用域中的函数,并让函数返回服务变量。我已经创建了一个js小提琴。您可以在下面找到代码。

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

其他回答

据我所知,你不需要做那么复杂的事情。您已经将foo从服务分配到作用域,并且由于foo是一个数组(反过来又是一个对象,它是通过引用分配的!). 所以,你所需要做的就是这样:

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

 }

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

我提出了这个问题,但事实证明,我的问题是,当我应该使用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");​

我编写了两个简单的实用程序服务,它们帮助我跟踪服务属性的更改。

如果您想跳过冗长的解释,可以直接访问jsfiddle

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

该服务在控制器中的使用方式如下: 假设你有一个服务“testService”,它的属性是“prop1”,“prop2”,“prop3”。您希望监视并将其分配给范围“prop1”和“prop2”。对于手表服务,它看起来像这样:

应用程序控制器(“检测”、“检测服务”、“观察”、“检测”); 功能测试表 扫帚美元。prop1 =测试服务。 扫帚美元。prop2 =测试服务。 扫帚美元。prop3 =测试服务。 观察(测试服务,[prop1, prop2], $scope, $scope]); )

应用 Watch obj很棒,但如果您的服务中有异步代码,它就不够了。在这种情况下,我使用了第二个实用程序,如下所示:

mod.service('apply', ['$timeout', ApplyService]); 函数ApplyService($timeout) { 返回函数apply() { 美元超时(函数(){}); }; }

我将在异步代码的末尾触发它以触发$digest循环。 像这样:

app.service('TestService', ['apply', TestService]); 函数TestService(应用){ 这一点。Apply = Apply; } TestService.prototype。Test3 =函数(){ setTimeout(函数(){ 这一点。Prop1 = 'changed_test_2'; 这一点。Prop2 = 'changed2_test_2'; 这一点。Prop3 = 'changed3_test_2'; this.apply ();//触发$digest循环 } .bind ()); }

所以,所有这些加在一起看起来就像这样(你可以运行它或打开小提琴):

// 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>

基于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();
  });
});

数组中。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;
};