varios entre diferencias diferencia controladores campusmvp angular2 angularjs structure angularjs-factory object-model angularjs-provider

controladores - diferencias entre angular y angularjs



AngularJS: ¿Cómo deberían estructurarse los controladores y las fábricas/servicios con un modelo de objetos jerárquico rico? (3)

Leí estos dos excelentes artículos:

El estado de los controladores angularjs por Jonathan Creamer

y

Repensando Controladores AngularJS por Todd Lema

En estos artículos, los autores hablan de la forma correcta de usar los controladores (convirtiéndolos en puentes anémicos entre la vista y el modelo) y las fábricas / servicios (donde realmente debería vivir la lógica comercial).

Esta es una gran información, y estaba muy emocionado de empezar a refactorizar los controladores en uno de mis proyectos, pero rápidamente descubrí que la estructura que se muestra en los artículos se rompe si tiene un modelo de objetos enriquecido.

Aquí hay un resumen de la configuración de "Rethinking Angularjs Controllers":

Aquí está el controlador:

app.controller(''InboxCtrl'', function InboxCtrl (InboxFactory) { var vm = this; vm.messages = InboxFactory.messages; vm.openMessage = function (message) { InboxFactory.openMessage(message); }; vm.deleteMessage = function (message) { InboxFactory.deleteMessage(message); }; InboxFactory .getMessages() .then(function () { vm.messages = InboxFactory.messages; }); });

y aquí está la fábrica:

app.factory(''InboxFactory'', function InboxFactory ($location, NotificationFactory) { factory.messages = []; factory.openMessage = function (message) { $location.search(''id'', message.id).path(''/message''); }; factory.deleteMessage = function (message) { $http.post(''/message/delete'', message) .success(function (data) { factory.messages.splice(index, 1); NotificationFactory.showSuccess(); }) .error(function () { NotificationFactory.showError(); }); }; factory.getMessages = function () { return $http.get(''/messages'') .success(function (data) { factory.messages = data; }) .error(function () { NotificationFactory.showError(); }); }; return factory; });

Esto es genial y debido a que los providers (la fábrica) son singletons, los datos se mantienen en todas las vistas y se puede acceder a ellos sin tener que volver a cargarlos desde la API.

Esto funciona bien si los messages son un objeto de nivel superior . ¿Pero qué pasa si no lo son? ¿Qué ocurre si se trata de una aplicación para navegar por las bandejas de entrada de otros usuarios? Tal vez usted sea un administrador y desee poder administrar y examinar las bandejas de entrada de cualquier usuario. Tal vez necesite cargar las bandejas de entrada de varios usuarios al mismo tiempo ¿Como funciona esto? El problema es que los mensajes de la bandeja de entrada se almacenan en el servicio, es decir, InboxFactory.messages .

¿Qué pasa si la jerarquía es así?

Organization | __________________|____________________ | | | Accounting Human Resources IT | | | ________|_______ _____|______ ______|________ | | | | | | | | | John Mike Sue Tom Joe Brad May Judy Jill | | | | | | | | | Inbox Inbox Inbox Inbox Inbox Inbox Inbox Inbox Inbox

Ahora los messages están en varios niveles en la jerarquía y no tienen ningún significado por sí mismos. No puede almacenar mensajes en la fábrica, InboxFactory.messages porque tiene que recuperar mensajes para varios usuarios a la vez.

Ahora tendrá un OrganizationFactory, un DepartmentFactory, un UserFactory y un InboxFactory. La recuperación de "mensajes" debe estar en el contexto de un user , que está en el contexto de un department , que está en el contexto de una organization . ¿Cómo y dónde deben almacenarse los datos? ¿Cómo debe ser recuperado?

Entonces, ¿cómo debería resolverse esto? ¿Cómo deberían estructurarse los controladores, las fábricas / servicios y los modelos de objetos ricos?

En este punto de mi pensamiento, me inclino por mantenerlo delgado y no tener un modelo de objeto rico. Simplemente almacene los objetos en el $ scope inyectado en el controlador, y si navega a una nueva vista, vuelva a cargar desde la API. Si necesita que algunos datos se conserven en las vistas, puede construir ese puente con un servicio o fábrica, pero no debería ser la forma en que hace la mayoría de las cosas.

¿Cómo lo han solucionado los demás? ¿Hay algún patrón por ahí para esto?


Después de MUCHOS retoques y probando diferentes enfoques, mi decisión final es que no debe persistir su modelo de objeto rico en las vistas.

Mantengo el modelo de objetos súper lean y carga lo que necesito para cada vista. Hay datos de alto nivel que mantengo a mi alrededor (información del usuario como nombre, id, correo electrónico, etc. y datos de organización, como la organización con la que están conectados), pero todo lo demás se carga para la vista actual.

Con este enfoque magro, esto es lo que parecería mi fábrica:

app.factory(''InboxFactory'', function InboxFactory ($location, NotificationFactory) { factory.messages = []; factory.openMessage = function (message) { $location.search(''id'', message.id).path(''/message''); }; factory.deleteMessage = function (message) { $http.post(''/message/delete'', message) .success(function (data) { NotificationFactory.showSuccess(); return data; }) .error(function () { NotificationFactory.showError(); return null; }); }; factory.getMessages = function (userId) { return $http.get(''/messages/user/id/''+userId) .success(function (data) { return data; }) .error(function () { NotificationFactory.showError(); return null; }); }; return factory; });

Y el controlador:

app.controller(''InboxCtrl'', function InboxCtrl (InboxFactory) { var vm = this; vm.messages = {}; vm.openMessage = function (message) { InboxFactory.openMessage(message); }; vm.deleteMessage = function (message) { InboxFactory.deleteMessage(message); }; InboxFactory .getMessages(userId) //you can get the userId from anywhere you want. .then(function (data) { vm.messages = data; }); });

Los beneficios hasta ahora son:

  • lógica de aplicación simplificada
  • magro y medio, ligero (solo carga lo que necesito para el estado actual)
  • menos uso de memoria, lo que se traduce en un mejor rendimiento general, especialmente en dispositivos móviles

Puede usar un modelo de objeto enriquecido, pero para los objetos que no son de nivel superior, sus fábricas deben exponer una API para crear nuevas instancias en lugar de ser utilizadas como singletons. Esto es algo contrario al diseño de muchas aplicaciones que ves en estos días, que son más funcionales que orientadas a objetos. No estoy comentando los pros y los contras de ninguno de los enfoques, y no creo que Angular te obligue a adoptar uno o el otro.

Su ejemplo, rediseñado, en pseudocódigo:

app.controller(''InboxCtrl'', function InboxCtrl (InboxFactory) { var inbox = InboxFactory.createInbox(); $scope.getMessages = function(){ inbox.getMessages() .then(...) $scope.deleteMessages = function(){ inbox.deleteMessages() .then(...) });


Su situación se vuelve mucho más sencilla si adopta un enfoque basado en rutas (a la ngRoute o algo similar). Considere esta alternativa - advertencia de código no probado:

app.config(function($routeProvider) { $routeProvider .when(''/inbox/:inboxId'', templateUrl: ''views/inbox.html'', controller: ''InboxCtrl'', controllerAs: ''inbox'', resolve: { inboxMessages: function(InboxFactory) { // Use use :inboxId route param if you need to work with multiple // inboxes. Taking some libery here, we''ll assuming // `InboxFactory.getMessages()` returns a promise that will resolve to // an array of messages; return InboxFactory.getMessages(); } } // ... other routes .otherwise: { // ... }; }); app.controller(''InboxCtrl'', function InboxCtrl (InboxFactory, inboxMessages) { var vm = this; vm.messages = inboxMessages; vm.openMessage = InboxFactory.openMessage; vm.deleteMessage = InboxFactory.deleteMessage; });

¡Mira qué delgado es el controlador ahora! Por supuesto, hice uso de una sintaxis más compacta en un par de lugares, pero esto resalta cómo nuestro controlador realmente está simplemente pegando cosas.

Podemos simplificar aún más las cosas deshaciéndonos de los InboxFactory.messages de InboxFactory.messages , ¿cuándo lo usaremos realmente? Solo tenemos la garantía de tener que rellenarlo después de que se resuelva InboxFactory.getMessages , así que solo dejemos que esta promesa se resuelva a los mensajes en sí.

Almacenar los datos en singletons de esta manera puede ser la solución más fácil en algunos casos, pero dificulta la vida cuando los datos se deben buscar sobre la marcha. Será mejor que se apoye en las API y las fábricas (como sugiere AlexMA), descargue los datos necesarios cada vez que cambie una ruta (por ejemplo, el usuario desea ver una bandeja de entrada diferente) e inyecte esos datos directamente en el controlador apropiado.

Otro beneficio de este formulario es que tenemos nuestros datos disponibles en el momento en que se crea una instancia del controlador. No tenemos que hacer malabarismos con los estados asíncronos o preocuparnos por poner mucho código en las devoluciones de llamada. Como corolario, podemos detectar los errores de carga de datos antes de mostrar una nueva vista de la bandeja de entrada y el usuario no se queda atascado en un estado medio horneado.

Más allá del punto de su pregunta, tenga en cuenta que la carga de saber cómo encaja la estructura de su modelo rico ya no es un problema del controlador. Simplemente obtiene algunos datos y expone un montón de métodos a la vista.