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.
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:
-
aService.foo
se establece en una nueva matriz cada vez, lo que hace que la referencia a ella sea obsoleta. -
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
sí 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.
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
<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
- 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);
}
- 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 $watch
ya que es una solución muy pesada. En su lugar proponen utilizar $emit
y $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 $emit
un user:change
estado.
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 $rootScope
y 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);
};