varios por pasar parametros entre ejemplos directivas controladores comunicacion angularjs watch angular-services

por - scope angularjs



AngularJS: ¿Cómo ver las variables de servicio? (21)

Aquí está mi enfoque genérico.

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

En su 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''];

Tengo un servicio, decir:

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

Y me gustaría usar foo para controlar una lista que se representa en HTML:

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

Para que el controlador detecte cuándo se actualiza aService.foo he aService.foo este patrón donde agrego un Servicio al $scope del controlador y luego 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; } }); }

Esto se siente largo, y lo he estado repitiendo en cada controlador que usa las variables del servicio. ¿Hay una mejor manera de lograr ver variables compartidas?


En un escenario como este, donde múltiples objetos desconocidos podrían estar interesados ​​en cambios, use $rootScope.$broadcast desde el elemento que se está cambiando.

En lugar de crear su propio registro de escuchas (que deben limpiarse en varios $ destroys), debería poder $broadcast desde el servicio en cuestión.

Aún debe codificar los $on controladores en cada escucha, pero el patrón se desacopla de varias llamadas a $digest y, por lo tanto, evita el riesgo de observadores de larga duración.

De esta manera, también, los oyentes pueden ir y venir desde el DOM y / o diferentes ámbitos secundarios sin que el servicio cambie su comportamiento.

** actualización: ejemplos **

Las transmisiones tendrían más sentido en los servicios "globales" que podrían impactar muchas otras cosas en su aplicación. Un buen ejemplo es un servicio de usuario donde hay una serie de eventos que podrían tener lugar, como inicio de sesión, cierre de sesión, actualización, inactivo, etc. Creo que aquí es donde las transmisiones tienen más sentido porque cualquier ámbito puede escuchar un evento, sin incluso inyectar el servicio, y no es necesario evaluar ninguna expresión o resultados de caché para inspeccionar los cambios. Simplemente dispara y olvida (así que asegúrate de que sea una notificación de "encender y olvidar", no algo que requiera acción)

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

El servicio anterior emitiría un mensaje a todos los ámbitos cuando la función save () se completara y los datos fueran válidos. Alternativamente, si se trata de un recurso $ o un envío ajax, mueva la llamada de difusión a la devolución de llamada para que se active cuando el servidor haya respondido. Las transmisiones se adaptan a ese patrón particularmente bien porque cada oyente simplemente espera el evento sin la necesidad de inspeccionar el alcance en cada resumen de $. El oyente se vería así:

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

Uno de los beneficios de esto es la eliminación de múltiples relojes. Si estuviera combinando campos o derivando valores como el ejemplo anterior, tendría que ver las propiedades del nombre y el apellido. Ver la función getUser () solo funcionaría si el objeto de usuario fuera reemplazado en las actualizaciones, no se activaría si el objeto de usuario simplemente tuviera sus propiedades actualizadas. En cuyo caso tendrías que hacer una observación profunda y eso es más intensivo.

$ broadcast envía el mensaje desde el ámbito al que se llama hacia abajo en cualquier ámbito secundario. Así que llamarlo desde $ rootScope se activará en todos los ámbitos. Si tuviera que emitir $ desde el alcance de su controlador, por ejemplo, se activaría solo en los ámbitos que heredan de su alcance del controlador. $ emit va en la dirección opuesta y se comporta de manera similar a un evento DOM, ya que mejora la cadena de alcance.

Tenga en cuenta que hay escenarios en los que $ broadcast tiene mucho sentido, y hay escenarios en los que $ watch es una mejor opción, especialmente si se trata de un ámbito aislado con una expresión de reloj muy específica.


Estoy usando un enfoque similar al de @dtheodot pero usando una promesa angular en lugar de pasar devoluciones de llamada

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

Luego, en cualquier lugar, solo use el myService.setFoo(foo) para actualizar foo en servicio. En tu controlador puedes usarlo como:

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

Los primeros dos argumentos de then son devoluciones de llamada de éxito y error, el tercero es notificar la devolución de llamada.

Referencia por $ q.


Me encontré con esta pregunta buscando algo similar, pero creo que merece una explicación detallada de lo que está sucediendo, así como algunas soluciones adicionales.

Cuando una expresión angular como la que $scope.foo está presente en el HTML, Angular configura automáticamente un $watch para $scope.foo , y actualizará el HTML cada vez que $scope.foo .

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

El problema no dicho aquí es que una de dos cosas está afectando aService.foo , por lo que los cambios no se detectan. Estas dos posibilidades son:

  1. aService.foo se establece en una nueva matriz cada vez, lo que hace que la referencia a ella sea obsoleta.
  2. aService.foo se está actualizando de tal manera que no se desencadena un ciclo $digest en la actualización.

Problema 1: Referencias desactualizadas

Teniendo en cuenta la primera posibilidad, suponiendo que se esté aplicando un $digest , si aService.foo siempre fue la misma matriz, el $watch configuración automática detectaría los cambios, como se muestra en el fragmento de código a continuación.

Solución 1-a: asegúrese de que la matriz u objeto sea el mismo objeto en cada actualización

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 puede ver, la repetición ng supuestamente asociada a aService.foo no se actualiza cuando cambia aService.foo , pero la repetición ng asociada a aService2.foo aService2.foo hace . Esto se debe a que nuestra referencia a aService.foo está actualizada, pero nuestra referencia a aService2.foo no lo está. Creamos una referencia a la matriz inicial con $scope.foo = aService.foo; , que luego fue descartado por el servicio en su próxima actualización, lo que significa que $scope.foo ya no hacía referencia a la matriz que queríamos más.

Sin embargo, si bien hay varias formas de asegurarse de que la referencia inicial se mantenga intacta, a veces puede ser necesario cambiar el objeto o la matriz. ¿O qué sucede si la propiedad de servicio hace referencia a una primitiva como una String o un Number ? En esos casos, no podemos simplemente confiar en una referencia. Entonces, ¿qué podemos hacer?

Varias de las respuestas dadas anteriormente ya dan algunas soluciones a ese problema. Sin embargo, personalmente estoy a favor de usar el método simple sugerido por Jin y las thetallweeks en los comentarios:

solo haga referencia a aService.foo en el marcado html

Solución 1-b: adjunte el servicio al ámbito y haga referencia a {service}.{property} en el HTML.

Es decir, solo haz esto:

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>

De esa manera, $watch resolverá aService.foo en cada $digest , que obtendrá el valor correctamente actualizado.

Esto es algo de lo que intentabas hacer con tu solución, pero de una forma mucho menos aproximada. Agregó un $watch innecesario en el controlador que pone explícitamente a foo en el $scope cuando cambia. No necesita ese $watch adicional cuando adjunta un aService lugar de un aService.foo al aService.foo de $scope , y se enlaza explícitamente con el aService.foo en el marcado.

Ahora eso está muy bien asumiendo que se está aplicando un ciclo de $digest . En mis ejemplos anteriores, usé el servicio de $interval de Angular para actualizar los arreglos, que automáticamente inicia un bucle de $digest después de cada actualización. Pero, ¿y si las variables de servicio (por cualquier motivo) no se actualizan dentro del "mundo angular"? En otras palabras, ¿ no tenemos un ciclo de $digest que se active automáticamente cada vez que cambie la propiedad del servicio?

Problema 2: Falta $digest

Muchas de las soluciones aquí resolverán este problema, pero estoy de acuerdo con Code Whisperer :

La razón por la que estamos usando un marco como Angular es para no crear nuestros propios patrones de observador

Por lo tanto, preferiría seguir utilizando la referencia aService.foo en el marcado HTML como se muestra en el segundo ejemplo anterior, y no tener que registrar una devolución de llamada adicional dentro del controlador.

Solución 2: Use un setter y getter con $rootScope.$apply()

Me sorprendió que nadie haya sugerido el uso de un setter y getter . Esta capacidad se introdujo en ECMAScript5 y, por lo tanto, ha existido durante años. Por supuesto, eso significa que si, por alguna razón, necesita ser compatible con navegadores realmente antiguos, entonces este método no funcionará, pero creo que los captadores y los definidores están muy poco utilizados en JavaScript. En este caso particular, podrían ser bastante útiles:

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>

Aquí agregué una variable ''privada'' en la función de servicio: realFoo . Este get se actualiza y recupera usando las funciones get foo() y set foo() respectivamente en el objeto de service .

Tenga en cuenta el uso de $rootScope.$apply() en la función set. Esto garantiza que Angular estará al tanto de cualquier cambio en service.foo . Si obtiene errores "inprog", consulte esta útil página de referencia , o si usa Angular> = 1.3, simplemente puede usar $rootScope.$applyAsync() .

También tenga cuidado con esto si aService.foo se actualiza con mucha frecuencia, ya que podría afectar significativamente el rendimiento. Si el rendimiento fuera un problema, podría configurar un patrón de observador similar al de las otras respuestas aquí usando el configurador.


Mientras enfrentaba un problema muy similar, observé una función en el alcance y tuve la función de devolver la variable de servicio. He creado un js fiddle . Puedes encontrar el código de abajo.

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


Por lo que puedo decir, no tienes que hacer algo tan detallado como eso. Ya ha asignado foo del servicio a su alcance y, dado que foo es una matriz (y, a su vez, un objeto, ¡se asigna por referencia!). Entonces, todo lo que necesitas hacer es algo como esto:

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

Si alguna, otra variable en este mismo Ctrl depende de que foo cambie, entonces sí, necesitaría un reloj para observar foo y hacer cambios en esa variable. Pero siempre y cuando sea una simple referencia la observación es innecesaria. Espero que esto ayude.


Puede ver los cambios dentro de la propia fábrica y luego emitir un cambio.

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

Luego en su controlador:

angular.module(''MyApp'').controller(''aController'', [''$rootScope'', function ($rootScope) { $rootScope.$on(''aFactory:keyChanged'', function currentCityChanged(event, value) { // do something }); }]);

De esta manera, coloca todo el código de fábrica relacionado dentro de su descripción, entonces solo puede confiar en la transmisión desde el exterior


Puedes insertar el servicio en $ rootScope y verlo:

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

En su controlador:

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

Otra opción es usar una biblioteca externa como: https://github.com/melanke/Watch.JS

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

Puede observar los cambios de uno, muchos o todos los atributos de objeto.

Ejemplo:

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


Siempre puedes usar el viejo patrón de observador si quieres evitar la tiranía y la sobrecarga de $watch .

En el servicio:

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

Y en el controlador:

function FooCtrl($scope, aService){ var updateFoo = function(){ $scope.foo = aService.foo; }; aService.registerObserverCallback(updateFoo); //service now in control of updating foo };


Sin relojes ni devoluciones de llamada del observador ( 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 porque estamos devolviendo un objeto del servicio en lugar de un valor. Cuando se devuelve un objeto JavaScript desde un servicio, Angular agrega relojes a todas sus propiedades.

También tenga en cuenta que estoy usando ''var self = this'' ya que necesito mantener una referencia al objeto original cuando se ejecuta el $ timeout, de lo contrario ''this'' se referirá al objeto de la ventana.


Sobre la base de la respuesta de Dtheodor, podría usar algo similar al siguiente para asegurarse de que no se olvide de cancelar el registro de la devolución de llamada ... Sin embargo, algunos pueden oponerse a pasar el $scope a un servicio.

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 es un método de extensión que se ve así:

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


// servicio: (nada especial aquí)

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


== ACTUALIZADO ==

Muy simple ahora en $ watch.

Pluma aquí

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


Eche un vistazo a este plunker: este es el ejemplo más simple que se me ocurre

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


He escrito dos servicios de utilidad simples que me ayudan a rastrear los cambios en las propiedades del servicio.

Si quieres saltarte la explicación larga, puedes ir jsfiddle a 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 servicio se usa en el controlador de la siguiente manera: Supongamos que tiene un servicio "testService" con las propiedades ''prop1'', ''prop2'', ''prop3''. Desea ver y asignar al alcance ''prop1'' y ''prop2''. Con el servicio de vigilancia se verá así:

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 Watch obj es excelente, pero no es suficiente si tiene un código asíncrono en su servicio. Para ese caso, uso una segunda utilidad que se ve así:

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

Lo activaría al final de mi código asíncrono para activar el bucle $ digest. Como eso:

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

Entonces, todo eso se verá así (puedes ejecutarlo o jsfiddle ):

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


He visto algunos patrones de observador terribles aquí que causan pérdidas de memoria en aplicaciones grandes.

Puede que sea un poco tarde pero es tan simple como esto.

La función de observación vigila los cambios de referencia (tipos primitivos) si desea ver algo como matriz empujar simplemente use:

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

Esto actualizará la referencia y actualizará el reloj desde cualquier lugar. Incluyendo un método de obtención de servicios. Cualquier cosa que sea primitiva será actualizada automáticamente.


Un poquito feo, pero he agregado el registro de variables de alcance a mi servicio para un interruptor:

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

Y luego en el controlador:

function MyCtrl($scope, myService) { $scope.name = ''Superhero''; $scope.myVar = false; myService.register($scope, ''myVar''); }


He encontrado una solución realmente genial en el otro hilo con un problema similar pero con un enfoque totalmente diferente. Fuente: AngularJS: $ ver dentro de la directiva no funciona cuando se cambia el valor de $ rootScope

Básicamente, la solución dice que NO se debe usar $watchya que es una solución muy pesada. En su lugar proponen utilizar $emity $on.

Mi problema fue ver una variable en mi servicio y reaccionar en directiva . Y con el método anterior es muy fácil!

Mi módulo / ejemplo de servicio:

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

Así que básicamente observo mi user- siempre que se establezca en un nuevo valor I $emitun user:changeestado.

Ahora en mi caso, en la directiva que utilicé:

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

Ahora en la directiva escucho sobre $rootScopey sobre el cambio dado, reacciono respectivamente. Muy fácil y elegante!


Llego tarde a la parte, pero encontré una manera mejor de hacerlo que la respuesta publicada anteriormente. En lugar de asignar una variable para mantener el valor de la variable de servicio, creé una función adjunta al ámbito, que devuelve la variable de servicio.

controlador

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

Creo que esto hará lo que quieras. Mi controlador sigue verificando el valor de mi servicio con esta implementación. Honestamente, esto es mucho más simple que la respuesta seleccionada.


Llegué a esta pregunta, pero resultó que mi problema era que estaba usando setInterval cuando debería haber estado usando el proveedor del intervalo angular $ Este es también el caso de setTimeout (use $ timeout en su lugar). Sé que no es la respuesta a la pregunta del OP, pero podría ayudar a algunos, ya que me ayudó.


Para aquellos como yo que solo buscan una solución simple, esto hace casi exactamente lo que espera de usar $ normal en los controladores. La única diferencia es que evalúa la cadena en su contexto javascript y no en un ámbito específico. Tendrá que inyectar $ rootScope en su servicio, aunque solo se utiliza para conectarse correctamente a los ciclos de resumen.

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