¿Dónde colocar los datos y el comportamiento del modelo? [tl; dr; Usar servicios]

Resuelto Nils Blum-Oeste asked hace 12 años • 7 respuestas

Estoy trabajando con AngularJS para mi último proyecto. En la documentación y los tutoriales, todos los datos del modelo se colocan en el alcance del controlador. Entiendo que tiene que estar ahí para estar disponible para el controlador y, por lo tanto, dentro de las vistas correspondientes.

Sin embargo, no creo que el modelo deba implementarse allí. Puede ser complejo y tener atributos privados, por ejemplo. Además, es posible que desee reutilizarlo en otro contexto/aplicación. Poner todo en el controlador rompe totalmente el patrón MVC.

Lo mismo se aplica al comportamiento de cualquier modelo. Si usara la arquitectura DCI y separara el comportamiento del modelo de datos, tendría que introducir objetos adicionales para mantener el comportamiento. Esto se haría introduciendo roles y contextos.

DCI == Interacción de colaboración de datos

Por supuesto, los datos y el comportamiento del modelo podrían implementarse con objetos javascript simples o cualquier patrón de "clase". ¿Pero cuál sería la forma en AngularJS de hacerlo? ¿Usando servicios?

Entonces todo se reduce a esta pregunta:

¿Cómo se implementan modelos desacoplados del controlador, siguiendo las mejores prácticas de AngularJS?

Nils Blum-Oeste avatar Jun 20 '12 10:06 Nils Blum-Oeste
Aceptado

Debe utilizar servicios si desea algo que puedan utilizar varios controladores. Aquí hay un ejemplo simple e ideado:

myApp.factory('ListService', function() {
  var ListService = {};
  var list = [];
  ListService.getItem = function(index) { return list[index]; }
  ListService.addItem = function(item) { list.push(item); }
  ListService.removeItem = function(item) { list.splice(list.indexOf(item), 1) }
  ListService.size = function() { return list.length; }

  return ListService;
});

function Ctrl1($scope, ListService) {
  //Can add/remove/get items from shared list
}

function Ctrl2($scope, ListService) {
  //Can add/remove/get items from shared list
}
Andrew Joslin avatar Jun 20 '2012 13:06 Andrew Joslin

Actualmente estoy probando este patrón, que, aunque no es DCI, proporciona un desacoplamiento clásico de servicio/modelo (con servicios para comunicarse con servicios web (también conocido como modelo CRUD) y un modelo que define las propiedades y métodos del objeto).

Tenga en cuenta que solo uso este patrón cuando el objeto modelo necesita métodos que funcionen en sus propias propiedades, que probablemente usaré en todas partes (como captadores/definidores mejorados). No estoy recomendando hacer esto para todos los servicios de forma sistemática.

EDITAR: Solía ​​​​pensar que este patrón iría en contra del mantra "El modelo angular es un simple objeto javascript antiguo", pero ahora me parece que este patrón está perfectamente bien.

EDITAR (2): Para ser aún más claro, uso una clase Modelo solo para factorizar captadores/definidores simples (por ejemplo, para usar en plantillas de vista). Para la lógica empresarial grande, recomiendo utilizar servicios separados que "conozcan" el modelo, pero que se mantengan separados de ellos y solo incluyan lógica empresarial. Llámelo capa de servicio de "experto en negocios" si lo desea

service/ElementServices.js (observe cómo se inyecta Element en la declaración)

MyApp.service('ElementServices', function($http, $q, Element)
{
    this.getById = function(id)
    {
        return $http.get('/element/' + id).then(
            function(response)
            {
                //this is where the Element model is used
                return new Element(response.data);
            },
            function(response)
            {
                return $q.reject(response.data.error);
            }
        );
    };
    ... other CRUD methods
}

model/Element.js (usando angularjs Factory, hecho para la creación de objetos)

MyApp.factory('Element', function()
{
    var Element = function(data) {
        //set defaults properties and functions
        angular.extend(this, {
            id:null,
            collection1:[],
            collection2:[],
            status:'NEW',
            //... other properties

            //dummy isNew function that would work on two properties to harden code
            isNew:function(){
                return (this.status=='NEW' || this.id == null);
            }
        });
        angular.extend(this, data);
    };
    return Element;
});
Ben G avatar Feb 13 '2013 11:02 Ben G

La documentación de Angularjs establece claramente:

A diferencia de muchos otros marcos, Angular no impone restricciones ni requisitos al modelo. No hay clases de las que heredar ni métodos de acceso especiales para acceder o cambiar el modelo. El modelo puede ser primitivo, hash de objeto o un tipo de objeto completo. En resumen, el modelo es un objeto JavaScript simple.

— Guía para desarrolladores de AngularJS - Conceptos V1.5 - Modelo

Entonces significa que depende de usted cómo declarar un modelo. Es un objeto Javascript simple.

Personalmente, no usaré los servicios Angular, ya que estaban destinados a comportarse como objetos únicos que puedes usar, por ejemplo, para mantener estados globales en tu aplicación.

S.C. avatar May 30 '2013 10:05 S.C.

DCI es un paradigma y, como tal, no existe una forma angularJS de hacerlo, ya sea que el lenguaje admita DCI o no. JS admite DCI bastante bien si está dispuesto a utilizar la transformación de fuente y tiene algunos inconvenientes si no lo está. Nuevamente, DCI no tiene más que ver con la inyección de dependencia que, por ejemplo, una clase C# y definitivamente tampoco es un servicio. Entonces, la mejor manera de hacer DCI con angulusJS es hacer DCI al estilo JS, que es bastante parecido a cómo se formula DCI en primer lugar. A menos que realice una transformación de fuente, no podrá hacerlo completamente ya que los métodos de rol serán parte del objeto incluso fuera del contexto, pero ese es generalmente el problema con la DCI basada en inyección de métodos. Si consulta fullOO.info, el sitio autorizado para DCI, puede echar un vistazo a las implementaciones de Ruby, que también utilizan la inyección de métodos, o puede consultar aquí para obtener más información sobre DCI. Es principalmente con ejemplos de RUby, pero el material de DCI es independiente de eso. Una de las claves de DCI es que lo que hace el sistema está separado de lo que es el sistema. Entonces, el objeto de datos es bastante tonto, pero una vez vinculado a un rol en un contexto, los métodos de rol hacen que cierto comportamiento esté disponible. Un rol es simplemente un identificador, nada más, y cuando se accede a un objeto a través de ese identificador, los métodos de rol están disponibles. No hay ningún objeto/clase de rol. Con la inyección de métodos, el alcance de los métodos de rol no es exactamente como se describe, pero sí cercano. Un ejemplo de un contexto en JS podría ser

function transfer(source,destination){
   source.transfer = function(amount){
        source.withdraw(amount);
        source.log("withdrew " + amount);
        destination.receive(amount);
   };
   destination.receive = function(amount){
      destination.deposit(amount);
      destination.log("deposited " + amount);
   };
   this.transfer = function(amount){
    source.transfer(amount);
   };
}
Rune FS avatar Jun 20 '2012 05:06 Rune FS

Como lo afirman otros carteles, Angular no proporciona una clase base lista para usar para modelar, pero puede proporcionar varias funciones de manera útil:

  1. Métodos para interactuar con una API RESTful y crear nuevos objetos
  2. Estableciendo relaciones entre modelos.
  3. Validar datos antes de persistir en el backend; También es útil para mostrar errores en tiempo real.
  4. Almacenamiento en caché y carga diferida para evitar realizar solicitudes HTTP inútiles
  5. Enlaces de máquina de estado (antes/después de guardar, actualizar, crear, nuevo, etc.)

Una biblioteca que hace bien todas estas cosas es ngActiveResource ( https://github.com/FacultyCreative/ngActiveResource ). Divulgación completa: escribí esta biblioteca y la he utilizado con éxito en la creación de varias aplicaciones a escala empresarial. Está bien probado y proporciona una API que debería resultar familiar para los desarrolladores de Rails.

Mi equipo y yo continuamos desarrollando activamente esta biblioteca y me encantaría ver que más desarrolladores de Angular contribuyan a ella y la prueben.

Brett Cassette avatar Jan 11 '2014 07:01 Brett Cassette