Estoy buscando la mejor práctica de cómo enlazar a una propiedad de servicio en AngularJS.
He trabajado a través de múltiples ejemplos para comprender cómo enlazar propiedades en un servicio creado con AngularJS.
A continuación tengo dos ejemplos de cómo enlazar a propiedades en un servicio; ambos trabajan. El primer ejemplo usa enlaces básicos y el segundo ejemplo usa $ scope. $ Watch para enlazar a las propiedades del servicio
¿Se prefiere alguno de estos ejemplos cuando se vincula a propiedades en un servicio o hay otra opción que no conozco que se recomendaría?
La premisa de estos ejemplos es que el servicio debe actualizar sus propiedades "lastUpdated" y "llamadas" cada 5 segundos. Una vez que se actualizan las propiedades del servicio, la vista debe reflejar estos cambios. Ambos ejemplos funcionan con éxito; Me pregunto si hay una mejor manera de hacerlo.
Enlace Básico
El siguiente código se puede ver y ejecutar aquí: http://plnkr.co/edit/d3c16z
<html>
<body ng-app="ServiceNotification" >
<div ng-controller="TimerCtrl1" style="border-style:dotted">
TimerCtrl1 <br/>
Last Updated: {{timerData.lastUpdated}}<br/>
Last Updated: {{timerData.calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.timerData = Timer.data;
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
</html>
La otra forma en que resolví el enlace a las propiedades del servicio es usar $ scope. $ Watch en el controlador.
$ alcance. $ reloj
El siguiente código se puede ver y ejecutar aquí: http://plnkr.co/edit/dSBlC9
<html>
<body ng-app="ServiceNotification">
<div style="border-style:dotted" ng-controller="TimerCtrl1">
TimerCtrl1<br/>
Last Updated: {{lastUpdated}}<br/>
Last Updated: {{calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.$watch(function () { return Timer.data.lastUpdated; },
function (value) {
console.log("In $watch - lastUpdated:" + value);
$scope.lastUpdated = value;
}
);
$scope.$watch(function () { return Timer.data.calls; },
function (value) {
console.log("In $watch - calls:" + value);
$scope.calls = value;
}
);
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
</html>
Soy consciente de que puedo usar $ rootscope. $ Broadcast en el servicio y $ root. $ On en el controlador, pero en otros ejemplos que he creado que usan $ broadcast / $ en la primera transmisión no son capturados por el controlador, pero las llamadas adicionales que se emiten se activan en el controlador. Si conoce una forma de resolver el problema de transmisión $ rootscope. $, Proporcione una respuesta.
Pero para repetir lo que mencioné anteriormente, me gustaría conocer las mejores prácticas de cómo vincular las propiedades de un servicio.
Actualizar
Esta pregunta fue originalmente formulada y respondida en abril de 2013. En mayo de 2014, Gil Birman proporcionó una nueva respuesta, que cambié como la respuesta correcta. Dado que la respuesta de Gil Birman tiene muy pocos votos positivos, mi preocupación es que las personas que lean esta pregunta ignoren su respuesta a favor de otras respuestas con muchos más votos. Antes de tomar una decisión sobre cuál es la mejor respuesta, le recomiendo la respuesta de Gil Birman.
fuente
Respuestas:
Considere algunos pros y contras del segundo enfoque :
0 en
{{lastUpdated}}
lugar de{{timerData.lastUpdated}}
, lo que podría ser fácilmente{{timer.lastUpdated}}
, lo que podría argumentar que es más legible (pero no discutamos ... Le doy a este punto una calificación neutral para que usted decida por usted mismo)+1 Puede ser conveniente que el controlador actúe como una especie de API para el marcado, de modo que si de alguna manera la estructura del modelo de datos cambia, puede (en teoría) actualizar las asignaciones de API del controlador sin tocar el parcial html.
-1 Sin embargo, la teoría no siempre se practican y por lo general me encuentro tener que modificar el marcado y la lógica del controlador cuando los cambios son llamados para, de todas formas . Entonces, el esfuerzo adicional de escribir la API niega su ventaja.
-1 Además, este enfoque no es muy SECO.
-1 Si desea vincular los datos a
ng-model
su código, se vuelva aún menos SECO ya que debe volver a empaquetarlos$scope.scalar_values
en el controlador para realizar una nueva llamada REST.-0.1 Hay un pequeño éxito en el rendimiento que crea observadores adicionales. Además, si las propiedades de datos se adjuntan al modelo que no necesitan ser observadas en un controlador en particular, crearán una sobrecarga adicional para los observadores profundos.
-1 ¿Qué pasa si varios controladores necesitan los mismos modelos de datos? Eso significa que tiene varias API para actualizar con cada cambio de modelo.
$scope.timerData = Timer.data;
está empezando a sonar muy tentador ahora mismo ... Profundicemos un poco más en ese último punto ... ¿De qué tipo de cambios de modelo estábamos hablando? ¿Un modelo en el back-end (servidor)? ¿O un modelo que se crea y vive solo en el front-end? En cualquier caso, lo que es esencialmente la API de mapeo de datos pertenece a la capa de servicio front-end (una fábrica o servicio angular). (Tenga en cuenta que su primer ejemplo, mi preferencia, no tiene una API de este tipo en la capa de servicio , lo cual está bien porque es lo suficientemente simple como para que no lo necesite).En conclusión , no todo tiene que estar desacoplado. Y en cuanto a desacoplar el marcado por completo del modelo de datos, los inconvenientes superan las ventajas.
Los controladores, en general , no deben estar llenos de
$scope = injectable.data.scalar
's. Por el contrario, se deben rociar con$scope = injectable.data
's,promise.then(..)
' s y$scope.complexClickAction = function() {..}
'sComo un enfoque alternativo para lograr el desacoplamiento de datos y, por lo tanto, la encapsulación de la vista, el único lugar en el que realmente tiene sentido desacoplar la vista del modelo es con una directiva . Pero incluso allí, no
$watch
valores escalares en las funcionescontroller
olink
. Eso no ahorrará tiempo ni hará que el código sea más fácil de mantener ni legible. Ni siquiera facilitará las pruebas, ya que las pruebas robustas en angular generalmente prueban el DOM resultante de todos modos . Por el contrario, en una demanda directiva de su API de datos en forma de objeto, y el favor usando sólo el$watch
ERS creado porng-bind
.Ejemplo http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio
ACTUALIZACIÓN : Finalmente volví a esta pregunta para agregar que no creo que ninguno de los enfoques sea "incorrecto". Originalmente había escrito que la respuesta de Josh David Miller era incorrecta, pero en retrospectiva sus puntos son completamente válidos, especialmente su punto sobre la separación de preocupaciones.
Dejando a un lado la separación de las preocupaciones (pero tangencialmente relacionadas), hay otra razón para la copia defensiva que no tuve en cuenta. Esta pregunta trata principalmente de leer datos directamente de un servicio. Pero, ¿qué sucede si un desarrollador de su equipo decide que el controlador necesita transformar los datos de una manera trivial antes de que la vista los muestre? (La cuestión de si los controladores deben transformar los datos es otra discusión). Si ella no hace una copia del objeto primero, puede causar involuntariamente regresiones en otro componente de la vista que consume los mismos datos.
Lo que esta pregunta realmente resalta son las deficiencias arquitectónicas de la aplicación angular típica (y realmente cualquier aplicación de JavaScript): acoplamiento estrecho de preocupaciones y mutabilidad de objetos. Recientemente me he enamorado de la aplicación de arquitectura con React y estructuras de datos inmutables. Hacerlo resuelve maravillosamente los siguientes dos problemas:
Separación de preocupaciones : un componente consume todos sus datos a través de accesorios y tiene poca o ninguna dependencia de los singletons globales (como los servicios angulares), y no sabe nada sobre lo que sucedió por encima de él en la jerarquía de vistas.
Mutabilidad : todos los accesorios son inmutables, lo que elimina el riesgo de mutación involuntaria de datos.
Angular 2.0 ahora está en camino de pedir prestado mucho de React para lograr los dos puntos anteriores.
fuente
Desde mi perspectiva,
$watch
sería la mejor práctica.De hecho, puede simplificar un poco su ejemplo:
Eso es todo lo que necesitas.
Como las propiedades se actualizan simultáneamente, solo necesita un reloj. Además, dado que provienen de un único objeto bastante pequeño, lo cambié para observar la
Timer.data
propiedad. El último parámetro pasado a$watch
le dice que compruebe la igualdad profunda en lugar de asegurarse de que la referencia sea la misma.Para proporcionar un poco de contexto, la razón por la que preferiría este método a colocar el valor del servicio directamente en el alcance es garantizar una separación adecuada de las preocupaciones. Su vista no debería necesitar saber nada sobre sus servicios para poder operar. El trabajo del controlador es unir todo; su trabajo es obtener los datos de sus servicios y procesarlos de la manera necesaria y luego brindarle a su vista los detalles que necesita. Pero no creo que su trabajo sea pasar el servicio directamente a la vista. De lo contrario, ¿qué hace el controlador incluso allí? Los desarrolladores de AngularJS siguieron el mismo razonamiento cuando optaron por no incluir ninguna "lógica" en las plantillas (por ejemplo,
if
declaraciones).Para ser justos, probablemente hay múltiples perspectivas aquí y espero otras respuestas.
fuente
{{lastUpdated}}
vs.{{timerData.lastUpdated}}
Timer.data
en un $ watch,Timer
tiene que definirse en el $ scope, porque la expresión de cadena que pasa a $ watch se evalúa contra el alcance. Aquí hay un plunker que muestra cómo hacer que eso funcione. El parámetro objectEquality se documenta aquí , tercer parámetro, pero en realidad no se explica demasiado bien.$watch
es bastante ineficiente. Vea las respuestas en stackoverflow.com/a/17558885/932632 y stackoverflow.com/questions/12576798/…Tarde a la fiesta, pero para futuros Googlers, no use la respuesta provista.
JavaScript tiene un mecanismo para pasar objetos por referencia, mientras que solo pasa una copia superficial para los valores "números, cadenas, etc.".
En el ejemplo anterior, en lugar de vincular los atributos de un servicio, ¿por qué no exponemos el servicio al alcance?
Este enfoque simple hará que Angular pueda hacer enlaces bidireccionales y todas las cosas mágicas que necesita. No piratee su controlador con observadores o marcado innecesario.
Y si le preocupa que su vista sobrescriba accidentalmente sus atributos de servicio, utilícela
defineProperty
para que sea legible, enumerable, configurable o defina getters y setters. Puede obtener mucho control al hacer que su servicio sea más sólido.Consejo final: si pasa más tiempo trabajando en su controlador que en sus servicios, lo está haciendo mal :(.
En ese código de demostración en particular que proporcionó, le recomendaría que haga:
Editar:
Como mencioné anteriormente, puede controlar el comportamiento de sus atributos de servicio utilizando
defineProperty
Ejemplo:
Ahora en nuestro controlador si lo hacemos
¡nuestro servicio cambiará el valor
propertyWithSetter
y también publicará el nuevo valor en la base de datos de alguna manera!O podemos tomar cualquier enfoque que queramos.
Consulte la documentación de MDN para
defineProperty
.fuente
$scope.model = {timerData: Timer.data};
solo adjuntarlo al modelo en lugar de directamente en Scope.Creo que esta pregunta tiene un componente contextual.
Si simplemente extrae datos de un servicio e irradia esa información a su vista, creo que enlazar directamente a la propiedad del servicio está bien. No quiero escribir mucho código repetitivo para simplemente asignar propiedades de servicio a propiedades de modelo para consumir en mi vista.
Además, el rendimiento en angular se basa en dos cosas. El primero es cuántos enlaces hay en una página. El segundo es lo caras que son las funciones getter. Misko habla de esto aquí
Si necesita realizar una lógica específica de instancia en los datos del servicio (a diferencia del masaje de datos aplicado dentro del propio servicio), y el resultado de esto afecta el modelo de datos expuesto a la vista, entonces diría que un $ watcher es apropiado, ya que siempre y cuando la función no sea terriblemente costosa. En el caso de una función costosa, sugeriría almacenar en caché los resultados en una variable local (al controlador), realizar sus operaciones complejas fuera de la función $ watcher, y luego vincular su alcance al resultado de eso.
Como advertencia, no debería colgar ninguna propiedad directamente de su $ scope. La
$scope
variable NO es tu modelo. Tiene referencias a tu modelo.En mi opinión, la "mejor práctica" para simplemente irradiar información del servicio hacia abajo para ver:
Y entonces tu vista contendría
{{model.timerData.lastupdated}}
.fuente
Sobre la base de los ejemplos anteriores, pensé en unir de manera transparente una variable de controlador a una variable de servicio.
En el siguiente ejemplo, los cambios en la
$scope.count
variable Controlador se reflejarán automáticamente en lacount
variable Servicio .En producción, en realidad estamos usando el enlace this para actualizar una identificación en un servicio que luego obtiene datos asincrónicamente y actualiza sus vars de servicio. Un enlace adicional significa que los controladores se actualizan automáticamente cuando el servicio se actualiza a sí mismo.
El código a continuación se puede ver trabajando en http://jsfiddle.net/xuUHS/163/
Ver:
Servicio / Controlador:
fuente
Creo que es una mejor manera de vincular el servicio en sí mismo en lugar de los atributos en él.
Este es el por qué:
Puedes jugarlo en este plunker .
fuente
Prefiero mantener a mis observadores lo menos posible. Mi razón se basa en mis experiencias y uno podría argumentarlo teóricamente.
El problema con el uso de los observadores es que puede usar cualquier propiedad en el alcance para llamar a cualquiera de los métodos en cualquier componente o servicio que desee.
En un proyecto del mundo real, muy pronto terminarás con una cadena de métodos no rastreables (mejor dicho, difíciles de rastrear) y cambios en los valores, lo que hace que el proceso de incorporación sea trágico.
fuente
Para vincular cualquier dato, lo que envía el servicio no es una buena idea (arquitectura), pero si lo necesita más, le sugiero 2 formas de hacerlo
1) puede obtener los datos que no están dentro de su servicio. Puede obtener datos dentro de su controlador / directiva y no tendrá ningún problema para vincularlos en cualquier lugar
2) puede usar eventos angularjs. Siempre que lo desee, puede enviar una señal (desde $ rootScope) y capturarla donde quiera. Incluso puede enviar datos sobre ese eventName.
Tal vez esto pueda ayudarle. Si necesita más con ejemplos, aquí está el enlace
http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html
fuente
Qué pasa
¿Dónde ParentScope es un servicio inyectado?
fuente
Las soluciones más elegantes ...
Además, escribo EDA (arquitecturas controladas por eventos), por lo que tiendo a hacer algo como lo siguiente [versión simplificada]:
Luego, pongo un oyente en mi controlador en el canal deseado y simplemente mantengo mi alcance local actualizado de esta manera.
En conclusión, no hay mucho de un "Best Practice" - más bien, su mayoría preferencia - siempre y cuando usted es mantener cosas sólidas y empleando acoplamiento débil. La razón por la que recomendaría este último código es porque los EDA tienen el acoplamiento más bajo posible por naturaleza. Y si no le preocupa demasiado este hecho, evitemos trabajar juntos en el mismo proyecto.
Espero que esto ayude...
fuente