AngularJS: Como assistir variáveis de serviço?

Eu tenho um serviço, diga:

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

  return service;
}]);

e eu gostaria de usar foo para controlar uma lista que é renderizada em HTML:

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

para que o controlador detecte quando aService.foo é actualizado, eu compus este padrão onde adiciono aService ao $scope do controlador e depois uso $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;
    }
  });
}
Isto parece ser de mão longa, e tenho-o repetido em todos os controladores que usam as variáveis do serviço. Existe uma maneira melhor de realizar a observação compartilhada variáveis?

Author: John Slegers, 2012-09-25

21 answers

Podes sempre usar o bom e velho padrão de observador se quiseres evitar a tirania e a sobrecarga de $watch.

Em serviço:

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

E no controlador:

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

  aService.registerObserverCallback(updateFoo);
  //service now in control of updating foo
};
 273
Author: dtheodor, 2013-08-21 21:53:51

Num cenário como este, onde objectos múltiplos/desconhecidos possam estar interessados em alterações, use $rootScope.$broadcast do item a ser alterado.

Em vez de criar o seu próprio registo de ouvintes (que têm de ser limpos em vários $Destroyers), você deve ser capaz de $broadcast a partir do serviço em questão.

Você ainda deve codificar os $on manipuladores em cada ouvinte, mas o padrão é dissociado de várias chamadas para $digest e assim evita o risco de observadores de longa duração.

Desta forma, também, os ouvintes podem vir e partir do DOM e/ou diferentes âmbitos de crianças sem que o serviço mude o seu comportamento.

** actualização: exemplos * *

As transmissões fariam mais sentido em serviços "globais" que poderiam ter impacto em inúmeras outras coisas na sua aplicação. Um bom exemplo é um serviço de usuário onde há uma série de eventos que podem ocorrer, tais como login, logout, update, idle, etc. Creio que é aqui que as transmissões fazem mais sentido. porque qualquer escopo pode ouvir para um evento, sem mesmo injetar o serviço, e ele não precisa avaliar quaisquer expressões ou resultados de cache para inspecionar para mudanças. Ele apenas dispara e esquece (por isso certifique-se de que é uma notificação de fogo e esquecimento, não algo que requer ação)
.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;
}]);

O serviço acima transmitiria uma mensagem para cada âmbito quando a função save() estivesse completa e os dados fossem válidos. Alternativamente, se for um recurso $ou um envio ajax, mova a transmissão ligue para o callback para que ele dispare quando o servidor tiver respondido. As transmissões se adequam a esse padrão particularmente bem, porque cada ouvinte apenas espera pelo evento sem a necessidade de inspecionar o escopo em cada $digest. O ouvinte pareceria:

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

 }]);
Um dos benefícios disto é a eliminação de vários relógios. Se você estivesse combinando campos ou derivando valores como o exemplo acima, você teria que assistir tanto as propriedades do primeiro nome e do último nome. A observar o a função getUser () só funcionaria se o objeto do usuário fosse substituído em atualizações, ele não dispararia se o objeto do usuário simplesmente tivesse suas propriedades atualizadas. Nesse caso você teria que fazer um relógio profundo e isso é mais intensivo. A$broadcast envia a mensagem do scope que é chamada para qualquer scope infantil. Então chamá-lo de $rootScope vai disparar em cada Mira. Se você fosse emitir $do escopo do seu controlador, por exemplo, ele dispararia apenas nos scopes que herda do teu controlador. $emit vai na direção oposta e se comporta de forma semelhante a um evento DOM em que ele borbulha a cadeia de escopo. Tenha em mente que existem cenários em que a $broadcast faz muito sentido, e há cenários em que o $watch é uma opção melhor - especialmente se num âmbito isolado com uma expressão de relógio muito específica.
 222
Author: Matt Pileggi, 2014-02-06 20:11:30
Estou a usar uma abordagem semelhante à do @dtheodot, mas usando uma promessa angular em vez de passar por callbacks.
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);
    }
})

Então, onde quer que apenas use o método myService.setFoo(foo) para actualizar foo em serviço. No seu controlador pode usá-lo como:

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

Os dois primeiros argumentos de then são sucesso e erros callbacks, o terceiro é notificar callback.

Referência para $q.

 43
Author: Krym, 2014-05-02 11:09:09
Sem Relógios ou chamadas de observação ([4]} 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>

Este JavaScript funciona à medida que estamos a passar um objecto de volta do serviço em vez de um valor. Quando um objeto JavaScript é devolvido de um serviço, Angular adiciona Relógios a todas as suas propriedades.

Note também que estou a usar 'var self = this' porque preciso de manter uma referência ao objecto original quando o '$timeout ' executar, caso contrário, 'isto' irá referir-se ao objecto da janela.

 38
Author: Zymotik, 2016-09-01 13:26:14
Pelo que sei, não tens de fazer algo tão elaborado. Você já atribuiu foo do serviço ao seu escopo e como foo é um array ( e, por sua vez, um objeto é atribuído por referência! ). Então, tudo o que precisas de fazer é algo assim:
function FooCtrl($scope, aService) {                                                                                                                              
  $scope.foo = aService.foo;

 }

Se alguma, outra variável neste mesmo Ctrl é dependente da mudança do foo, então sim, você precisaria de um relógio para observar o foo e fazer alterações a essa variável. Mas desde que seja uma simples referência ver é desnecessário. Espero que isto ajude.

 28
Author: ganaraj, 2012-09-25 08:11:57
Deparei-me com esta questão à procura de algo semelhante, mas acho que merece uma explicação completa do que está a acontecer, bem como algumas soluções adicionais.

Quando uma expressão angular como a que usou está presente no HTML, o Angular configura automaticamente um $watch para $scope.foo, e irá actualizar o HTML sempre que o $scope.foo mudar.

<div ng-controller="FooCtrl">
  <div ng-repeat="item in foo">{{ item }}</div>
</div>
A questão não dita aqui é que uma de duas coisas está afetando aService.foo de tal forma que as mudanças não são detectadas. Estas duas possibilidades são:
  1. aService.foo está a ajustar-se a uma nova matriz cada vez, fazendo com que a referência a ela esteja desactualizada.
  2. aService.foo está a ser actualizado de forma a que um ciclo $digest não seja activado na actualização.

Problema 1: Referências Ultrapassadas

Considerando a primeira possibilidade, assumindo que um $digest está a ser aplicado, se aService.foo fosse sempre a mesma matriz, o conjunto automático $watch iria detectar as alterações, como mostrado no excerto de código abaixo.

Solução 1-A: certifique - se que a matriz ou objecto é o mesmo objecto em cada actualização

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>

Como pode ver, a ng-repeat supostamente ligada a aService.foo não actualiza quando aService.foo muda, mas a ng-repeat ligada a aService2.foo faz . Isto porque a nossa referência a {[13] } está desactualizada, mas a nossa referência a aService2.foo não está. Nós criamos uma referência à matriz inicial com $scope.foo = aService.foo;, que foi descartada pelo serviço na próxima atualização, o que significa que $scope.foo já não referenciou a matriz que queríamos.

No entanto, embora existam várias formas de garantir que a referência inicial é mantida em tacto, por vezes pode ser necessário alterar o objecto ou a matriz. Ou se a propriedade de serviço faz referência a um primitivo como um String ou Number? Nesses casos, não podemos simplesmente confiar numa referência. Então o que podemos fazer?

Várias das respostas dadas anteriormente já dava algumas soluções para esse problema. No entanto, sou pessoalmente a favor de usar o método simples sugerido por Jin e as semanas nos comentários:

Apenas referência ao serviço.foo no HTML markup

Solução 1-b: anexar o serviço ao âmbito e referência {service}.{property} no HTML.

Ou seja, faz isto:

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="[email protected]" 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>

Assim, o $watch irá resolver aService.foo em cada $digest, o que irá obter o valor correctamente actualizado.

Isto é mais ou menos o que estavas a tentar fazer com o teu trabalho, mas muito menos em termos redondos. Você adicionou um $watch desnecessário no controlador que coloca explicitamente foo No $scope sempre que ele muda. Não precisas desse extra. você anexou aService em vez de aService.foo ao $scope, e se vinculou explicitamente a aService.foo na marcação.
Isso é bom, assumindo que um ciclo está a ser aplicado. Em meus exemplos acima, eu usei Angular's$interval serviço para actualizar as matrizes, que inicia automaticamente um ciclo $digest após cada actualização. Mas e se as variáveis de serviço (por qualquer razão) não estão sendo atualizadas dentro do "mundo Angular". Em outras palavras, nós não temos um $digest o ciclo é activado automaticamente sempre que a propriedade do serviço muda?

Problema 2: Desaparecido $digest

Muitas das soluções aqui resolverão este problema, mas concordo com o Sussurrador de código.:
A razão pela qual estamos a usar uma estrutura como Angular é para não cozinharmos os nossos próprios padrões de observação.

Portanto, eu preferiria continuar a usar a referência aService.foo na marcação HTML como mostrado no segundo exemplo acima, e não ter que registrar uma callback adicional dentro do controlador.

Solução 2: utilizar um setter e getter com $rootScope.$apply()

Fiquei surpreendido por ainda ninguém ter sugerido o uso de umsetter egetter . Esta capacidade foi introduzida na ECMAScript5, e, portanto, já existe há anos. Claro, isso significa que se, por qualquer razão, você precisa apoiar navegadores realmente velhos, então este método não vai funcionar, mas eu sinto como getters e setters são muito subutilizado em JavaScript. Neste caso específico, podem ser bastante úteis:

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>

Aqui adicionei uma variável 'privada' na função de serviço: realFoo. Esta obtenção é actualizada e recuperada usando as funções get foo() e set foo() respectivamente no objecto service.

Note a utilização de $rootScope.$apply() na função set. Isto garante que o Angular esteja ciente de quaisquer alterações a service.foo. Se você obter erros 'inprog' veja em esta página de referência útil , ou se usar o Angular > = 1, 3 pode apenas usar $rootScope.$applyAsync().

Tenha também Cuidado com isto se aService.foo estiver a ser actualizado com muita frequência, uma vez que isso pode ter um impacto significativo no desempenho. Se o desempenho fosse um problema, você poderia configurar um padrão de observador semelhante às outras respostas aqui usando o setter.

 28
Author: NanoWizard, 2018-08-27 15:18:43

Pode inserir o serviço em $rootScope e ver:

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

No seu controlador:

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

Outra opção é usar uma biblioteca externa como: https://github.com/melanke/Watch.JS

Funciona com: IE 9+, FF 4+, SF 5+, WebKit, CH 7+, OP 12+, BESEN, Node.JS, Rinoceronte 1, 7+

Você pode observar as mudanças de um, muitos ou todos os atributos do objeto.

Exemplo:

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");​
 8
Author: Rodolfo Jorge Nemer Nogueira, 2014-09-13 16:30:37
Pode ver as mudanças dentro da própria fábrica e depois transmitir uma mudança.
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;
});

Depois no seu controlador:

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

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

Desta forma você coloca todo o código de fábrica relacionado dentro da sua descrição então você só pode confiar na transmissão de fora

 6
Author: Flavien Volken, 2015-02-10 11:08:48

==actualizado==

Muito simples agora em $ watch.

Pen aqui.

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 + '!');
  });
}]);
 5
Author: hayatbiralem, 2016-07-23 14:04:30

Com base na resposta dedo dtheodor Você poderia usar algo semelhante ao abaixo para garantir que você não se esqueça de desbloquear o callback... Alguns podem opor-se a passar o $scope para um serviço embora.

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

Matriz.Remover é um método de extensão que se parece com este:

/**
 * 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;
};
 4
Author: Jamie, 2013-09-02 11:37:48
Eis a minha abordagem genérica.
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');
        }

    }]);

No Seu Controlador

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'];
 2
Author: elitechance, 2013-09-17 03:44:30
[[[2]] enquanto enfrentava um problema muito semelhante, eu assisti a uma função no escopo e fiz com que a função retornasse a variável de serviço. Eu criei um js fiddle. você pode encontrar o código abaixo.
    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++;
    });
});
 2
Author: chandings, 2014-12-29 08:01:43

Cheguei a esta questão, mas afinal o meu problema era que eu estava a usar setInterval quando devia estar a usar o fornecedor do intervalo angular$. Este também é o caso do setTimeout (use $timeout em vez disso). Sei que não é a resposta à pergunta da operação, mas pode ajudar alguns, pois ajudou-me.

 2
Author: honkskillet, 2015-02-18 01:02:36
Encontrei uma grande solução no outro tópico com um problema semelhante, mas uma abordagem totalmente diferente. Fonte: AngularJS: $ watch within directive is not working when $rootScope value is changed

Basicamente a solução lá diz para não usar $watch Como é uma solução muito pesada. em vez disso propõem-se usar $emit e $on.

O meu problema era ver uma variável no meu Serviço e reaja na Directiva. E com o método acima é muito fácil!

O meu exemplo de Módulo/serviço:

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

Então basicamente euassisto o meu user - sempre que estiver definido para um novo valor I $emit a user:change estado.

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

Agora na Directiva ouço as $rootScope esobre A DADA mudança - eu reajo, respectivamente. Muito fácil e elegante!

 2
Author: Atais, 2017-05-23 10:31:31
Para aqueles como eu que apenas procuram uma solução simples, isto faz quase exatamente o que você espera de usar um relógio $normal em controladores. A única diferença é que ele avalia a string em seu contexto javascript e não em um escopo específico. Você vai ter que injetar o rootScope $em seu serviço, embora ele só é usado para ligar-se aos ciclos digest corretamente.
function watch(target, callback, deep) {
    $rootScope.$watch(function () {return eval(target);}, callback, deep);
};
 1
Author: Justus Wingert, 2014-09-29 09:26:18

/ / Serviço: (nada de especial AQUI)

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;
  });
});
 1
Author: Nawlbergs, 2016-05-13 18:54:11

Um pouco feio, mas adicionei o registo de variáveis de âmbito ao meu serviço para uma toggle:

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

E depois no controlador:

function MyCtrl($scope, myService) {
    $scope.name = 'Superhero';
    $scope.myVar = false;
    myService.register($scope, 'myVar');
}
 1
Author: nclu, 2016-07-18 23:51:48
Eu vi alguns padrões de observadores terríveis aqui que causam vazamentos de memória em grandes aplicações. Posso chegar um pouco atrasado, mas é tão simples como isto.

A função de observação observa as alterações de referência (tipos primitivos) se quiser ver algo como o 'array push', basta usar:

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

Isto irá actualizar a referência e actualizar o relógio de qualquer lado. Incluindo um método de getter de serviços. Tudo o que for primitivo será actualizado. automaticamente.

 0
Author: Oliver Dixon, 2015-12-26 12:35:25
Estou atrasado para a parte, mas encontrei uma maneira mais simpática de fazer isto do que a resposta postada acima. Em vez de atribuir uma variável para manter o valor da variável de serviço, eu criei uma função ligada ao escopo, que retorna a variável de serviço.

Controlador

$scope.foo = function(){
 return aService.foo;
}
Acho que isto vai fazer o que quiseres. O meu controlador continua a verificar o valor do meu serviço com esta implementação. Honestamente, isto é muito mais simples do que a resposta selecionada.
 0
Author: alaboudi, 2016-08-18 20:00:13
Escrevi dois serviços de utilidade pública simples que me ajudam a acompanhar as alterações de propriedades do serviço.

Se quiser saltar a longa explicação, pode ir directamente para 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;
  };
}

Este serviço é utilizado no controlador da seguinte forma:: Suponha que você tem um serviço "testService" com as propriedades 'prop1', 'prop2', 'prop3'. Você quer assistir e atribuir ao escopo 'prop1' e "prop2". Com o serviço de vigilância vai parecer assim:

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. aplicar Veja obj é ótimo, mas não é suficiente se você tem código assíncrono em seu serviço. Para esse caso, eu uso um segundo utilitário que se parece com isso:

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

function ApplyService($timeout) {
  return function apply() {
    $timeout(function() {});
  };
}
Eu activava - o no fim do meu código async para activar o circuito $digest. Comer:

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));
}
Então, tudo isso junto parecerá assim (você pode executá-lo ou abrir o violino):

// 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
Author: Max, 2016-08-25 10:51:50
Este é o exemplo mais simples que eu poderia pensar de

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;
});
 0
Author: anandharshan, 2016-09-07 11:44:37