Tengo un servicio, digamos:
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 aService.foo
se actualiza, he improvisado este patrón donde agrego un servicio al controlador $scope
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?
angularjs
watch
angular-services
berto
fuente
fuente
aService.foo
en el marcado html? (como este: plnkr.co/edit/aNrw5Wo4Q0IxR2loipl5?p=preview )Respuestas:
Siempre puede usar el viejo patrón de observador si desea evitar la tiranía y la sobrecarga de
$watch
.En el servicio:
Y en el controlador:
fuente
$destory
evento en el alcance y agregue un método para cancelar el registro aaService
$watch
vs patrón de observador es simplemente elegir si sondear o empujar, y es básicamente una cuestión de rendimiento, así que úselo cuando el rendimiento sea importante. Utilizo un patrón de observador cuando, de lo contrario, tendría que "observar" en profundidad objetos complejos. Adjunto servicios completos al $ scope en lugar de mirar valores de servicio únicos. Evito los $ watch de angular como el diablo, ya hay suficiente de eso en las directivas y en el enlace de datos angular nativo.En un escenario como este, donde los objetos múltiples / desconocidos pueden estar interesados en los cambios, use
$rootScope.$broadcast
desde el elemento que se está cambiando.En lugar de crear su propio registro de oyentes (que deben limpiarse en varias destrucciones de $), debería poder hacerlo
$broadcast
desde el servicio en cuestión.Todavía debe codificar los
$on
controladores en cada escucha, pero el patrón está desacoplado de múltiples llamadas$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 afectar innumerables 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, inactividad, etc. Creo que aquí es donde las transmisiones tienen más sentido porque cualquier ámbito puede escuchar un evento, sin incluso inyectando el servicio, y no necesita evaluar ninguna expresión o resultado de la memoria caché para inspeccionar los cambios. Simplemente se dispara y se olvida (así que asegúrese de que sea una notificación de disparar y olvidar, no algo que requiera acción)
El servicio anterior transmitiría un mensaje a cada ámbito 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 particularmente bien a ese patrón porque cada oyente solo espera el evento sin la necesidad de inspeccionar el alcance en cada resumen de $. El oyente se vería así:
Uno de los beneficios de esto es la eliminación de múltiples relojes. Si estaba combinando campos o derivando valores como el ejemplo anterior, tendría que ver las propiedades de nombre y apellido. Ver la función getUser () solo funcionaría si el objeto del usuario se reemplazara en las actualizaciones, no se dispararía si el objeto del usuario simplemente tuviera sus propiedades actualizadas. En cuyo caso tendrías que hacer una vigilancia profunda y eso es más intenso.
$ broadcast envía el mensaje desde el ámbito en el que se invoca a cualquier ámbito secundario. Entonces llamarlo desde $ rootScope se disparará en cada ámbito. Si tuviera que emitir $ desde el alcance de su controlador, por ejemplo, solo se dispararía en los ámbitos que heredan del alcance de su controlador. $ emit va en la dirección opuesta y se comporta de manera similar a un evento DOM, ya que burbujea en 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 está en un ámbito aislado con una expresión de reloj muy específica.
fuente
Estoy usando un enfoque similar a @dtheodot pero usando promesa angular en lugar de pasar devoluciones de llamada
Luego, donde sea, solo use el
myService.setFoo(foo)
método para actualizarfoo
el servicio. En su controlador puede usarlo como:Los primeros dos argumentos
then
son devoluciones de llamada de éxito y error, el tercero es notificar devolución de llamada.Referencia para $ q.
fuente
$scope.$watch
una variable de servicio no pareciera funcionar (el alcance que estaba viendo era un modo que heredó de$rootScope
), esto funcionó. Buen truco, ¡gracias por compartir!Sin relojes ni devoluciones de llamadas de observadores ( http://jsfiddle.net/zymotik/853wvv7s/ ):
JavaScript:
HTML
Este JavaScript funciona cuando estamos devolviendo un objeto del servicio en lugar de un valor. Cuando se devuelve un objeto JavaScript de 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 tiempo de espera $, de lo contrario 'this' se referirá al objeto de la ventana.
fuente
$scope.count = service.count
no funciona.$scope.data = service.data
<p>Count: {{ data.count }}</p>
Me topé 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 usó está presente en el HTML, Angular configura automáticamente un
$watch
for$scope.foo
y actualizará el HTML cada vez que$scope.foo
cambie.El problema no mencionado aquí es que una de las dos cosas está afectando de
aService.foo
tal manera 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 no esté actualizada.aService.foo
se actualiza de tal manera que$digest
no se desencadena un ciclo en la actualización.Problema 1: referencias obsoletas
Teniendo en cuenta la primera posibilidad, suponiendo que
$digest
se esté aplicando a, siaService.foo
siempre fue la misma matriz, el conjunto automático$watch
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
Mostrar fragmento de código
Como puede ver, la ng-repeat supuestamente asociada a
aService.foo
no se actualiza cuandoaService.foo
cambia, pero la ng-repeat adjunta aaService2.foo
sí . Esto se debe a que nuestra referencia aaService.foo
está desactualizada, pero nuestra referencia aaService2.foo
no lo está. Creamos una referencia a la matriz inicial con$scope.foo = aService.foo;
, que luego fue descartada por el servicio en su próxima actualización, lo que significa que$scope.foo
ya no hace referencia a la matriz que queríamos.Sin embargo, si bien hay varias maneras de asegurarse de que la referencia inicial se mantenga intacta, a veces puede ser necesario cambiar el objeto o la matriz. ¿O qué pasa si la propiedad del servicio hace referencia a una primitiva como a
String
oNumber
? 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 thetallweeks en los comentarios:
Solución 1-b: Adjunte el servicio al ámbito y haga referencia
{service}.{property}
en el HTML.Es decir, solo haz esto:
HTML:
JS:
Mostrar fragmento de código
De esa manera,
$watch
se resolveráaService.foo
en cada uno$digest
, lo que obtendrá el valor actualizado correctamente.Esto es algo de lo que estaba tratando de hacer con su solución, pero de una manera mucho menos redonda. Ha añadido una innecesaria
$watch
en el controlador que pone explícitamentefoo
en el$scope
cada vez que cambia. No necesita ese extra$watch
cuando se adjunta enaService
lugar deaService.foo
al$scope
y se vincula explícitamenteaService.foo
en el marcado.Ahora eso está muy bien suponiendo
$digest
que se esté aplicando un ciclo. En mis ejemplos anteriores, utilicé el$interval
servicio de Angular para actualizar las matrices, que inicia automáticamente un$digest
bucle después de cada actualización. Pero, ¿qué pasa si las variables de servicio (por cualquier razón) no se actualizan dentro del "mundo angular". En otras palabras, ¿ no se$digest
activa automáticamente un ciclo cada vez que cambia la propiedad del servicio?Problema 2: falta
$digest
Muchas de las soluciones aquí resolverán este problema, pero estoy de acuerdo con Code Whisperer :
Por lo tanto, preferiría seguir usando la
aService.foo
referencia 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, existe desde hace años. Por supuesto, eso significa que si, por cualquier razón, necesita admitir navegadores realmente antiguos, entonces este método no funcionará, pero siento que los captadores y configuradores están muy infrautilizados en JavaScript. En este caso particular, podrían ser bastante útiles:
Mostrar fragmento de código
Aquí he añadido una variable 'privado' en la función de servicio:
realFoo
. Esto se actualiza y recupera utilizando las funcionesget foo()
yset foo()
respectivamente en elservice
objeto.Tenga en cuenta el uso de
$rootScope.$apply()
en la función set. Esto asegura que Angular estará al tanto de cualquier cambio enservice.foo
. Si obtiene errores 'inprog', consulte esta página de referencia útil , 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 eso podría afectar significativamente el rendimiento. Si el rendimiento fuera un problema, podría configurar un patrón de observación similar a las otras respuestas aquí usando el configurador.fuente
services
no busca propiedades que pertenezcan al servicio en sí.Por lo que puedo decir, no tienes que hacer algo tan elaborado como eso. Ya ha asignado foo desde el servicio a su ámbito y dado que foo es una matriz (¡y a su vez un objeto se le asigna por referencia!). Entonces, todo lo que necesitas hacer es algo como esto:
Si alguna, otra variable en este mismo Ctrl depende del cambio de foo, entonces sí, necesitaría un reloj para observarlo y hacer cambios a esa variable. Pero mientras sea una referencia simple, la observación es innecesaria. Espero que esto ayude.
fuente
$watch
trabajar con un primitivo. En su lugar, he definido un método en el servicio que devolvería el valor simple:somePrimitive() = function() { return somePrimitive }
Y he asignado una propiedad de $ alcance a ese método:$scope.somePrimitive = aService.somePrimitive;
. Luego usé el método de alcance en el HTML:<span>{{somePrimitive()}}</span>
$scope.foo = aService.foo
no actualiza la variable de alcance automáticamente.Puede insertar el servicio en $ rootScope y ver:
En tu controlador:
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 del objeto.
Ejemplo:
fuente
Puede ver los cambios dentro de la propia fábrica y luego transmitir un cambio
Luego en su controlador:
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
fuente
== ACTUALIZADO ==
Muy simple ahora en $ watch.
Pluma aquí .
HTML:
JavaScript:
fuente
Basándose en la respuesta de dtheodor, podría usar algo similar a lo siguiente para asegurarse de que no se olvide de anular el registro de la devolución de llamada ... Sin
$scope
embargo, algunos pueden oponerse a pasar el servicio a un servicio.Array.remove es un método de extensión que se ve así:
fuente
Aquí está mi enfoque genérico.
En su controlador
fuente
Para aquellos como yo que solo buscan una solución simple, esto hace casi exactamente lo que espera de usar $ watch normal en los controladores. La única diferencia es que evalúa la cadena en su contexto de JavaScript y no en un ámbito específico. Tendrá que inyectar $ rootScope en su servicio, aunque solo se usa para conectar correctamente los ciclos de resumen.
fuente
Mientras enfrentaba un problema muy similar, vi una función en su alcance y la función devolvió la variable de servicio. He creado un violín js . Puedes encontrar el código a continuación.
fuente
Llegué a esta pregunta, pero resultó que mi problema era que estaba usando setInterval cuando debería haber estado usando el proveedor de intervalo angular $. Este también es 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ó.
fuente
setTimeout
, o cualquier otra función no angular, pero no olvide envolver el código en la devolución de llamada$scope.$apply()
.He encontrado una solución realmente excelente en el otro hilo con un problema similar pero un enfoque totalmente diferente. Fuente: AngularJS: $ watch dentro de la directiva no funciona cuando se cambia el valor de $ rootScope
Básicamente, la solución allí le dice que NO la use,
$watch
ya que es una solución muy pesada. En su lugar , proponen usar$emit
y$on
.Mi problema era observar una variable en mi servicio y reaccionar en la directiva . ¡Y con el método anterior es muy fácil!
Mi módulo / ejemplo de servicio:
Así que básicamente miro mi
user
- cada vez que se establece en un nuevo valor,$emit
unuser:change
estado.Ahora en mi caso, en la directiva que utilicé:
Ahora en la directiva escucho sobre
$rootScope
y sobre el cambio dado, reacciono respectivamente. Muy facil y elegante!fuente
// servicio: (nada especial aquí)
// ctrl:
fuente
Un poco feo, pero he agregado el registro de variables de alcance a mi servicio para alternar:
Y luego en el controlador:
fuente
this
de ese servicio en lugar deself
?return this;
salir de sus constructores ;-)Echa un vistazo a este plunker :: este es el ejemplo más simple que se me ocurre
http://jsfiddle.net/HEdJF/
fuente
He visto algunos patrones de observación terribles que causan pérdidas de memoria en aplicaciones grandes.
Puede que llegue un poco tarde, pero es tan simple como esto.
La función de observación busca cambios de referencia (tipos primitivos) si desea ver algo como la inserción de matriz 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. Todo lo que sea primitivo se actualizará automáticamente.
fuente
Llego tarde a la parte, pero encontré una mejor manera de hacerlo que la respuesta publicada anteriormente. En lugar de asignar una variable para contener el valor de la variable de servicio, creé una función adjunta al ámbito, que devuelve la variable de servicio.
controlador
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.
fuente
He escrito dos servicios de utilidad simples que me ayudan a rastrear los cambios en las propiedades del servicio.
Si desea omitir la larga explicación, puede ir directamente a jsfiddle
Este servicio se utiliza en el controlador de la siguiente manera: supongamos que tiene un servicio "testService" con las propiedades 'prop1', 'prop2', 'prop3'. Desea ver y asignar al ámbito 'prop1' y 'prop2'. Con el servicio de reloj se verá así:
Lo activaría al final de mi código asíncrono para activar el bucle $ digest. Como eso:
Entonces, todo eso junto se verá así (puede ejecutarlo o abrir el violín ):
fuente