javascript - attribute - title css
¿Dónde poner el modelo de datos y comportamiento? (8)
Estoy trabajando con AngularJS para mi último proyecto. En la documentación y los tutoriales, todos los datos del modelo se colocan en el ámbito del controlador. Entiendo que debe estar disponible para que esté 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, uno podría querer reutilizarlo en otro contexto / aplicación. Poner todo en el controlador rompe totalmente el patrón MVC.
Lo mismo es válido para el comportamiento de cualquier modelo. Si usara la arquitectura DCI y el comportamiento separado del modelo de datos, tendría que introducir objetos adicionales para mantener el comportamiento. Esto se haría introduciendo roles y contextos.
Por supuesto, los datos y el comportamiento del modelo podrían implementarse con objetos de JavaScript simples o cualquier patrón de "clase". Pero, ¿cuál sería la forma AngularJS de hacerlo? ¿Utilizando servicios?
Así que todo se reduce a esta pregunta:
¿Cómo implementa modelos desacoplados del controlador, siguiendo las mejores prácticas de AngularJS?
Actualmente estoy probando este patrón, que, aunque no es DCI, proporciona un desacoplamiento clásico de servicio / modelo (con servicios para hablar con servicios web (también conocido como modelo CRUD), y modelo que define las propiedades y métodos del objeto).
Tenga en cuenta que solo uso este patrón cada vez que el objeto modelo necesite métodos que funcionen en sus propias propiedades, que probablemente usaré en todas partes (como mejor getter / setters). No estoy abogando por hacer esto sistemáticamente para cada servicio.
EDITAR: Solía pensar que este patrón iría en contra del mantra "El modelo angular es un objeto javascript antiguo", pero me parece que este patrón está perfectamente bien.
EDITAR (2): Para ser aún más claro, utilizo una clase de modelo solo para factorizar los captadores / definidores simples (por ejemplo: para usar en plantillas de vista). Para la lógica de grandes negocios, recomiendo usar servicios separados que "conozcan" el modelo, pero se mantengan separados de ellos, y solo incluyan la lógica de negocios. Llámelo capa de servicio "experto en negocios" si quiere.
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;
});
Como lo indican otros carteles, Angular no proporciona una clase base lista para usar para modelar, pero uno puede proporcionar varias funciones de manera útil:
- Métodos para interactuar con una API RESTful y crear nuevos objetos
- Estableciendo relaciones entre modelos.
- Validando los datos antes de persistir al backend; También es útil para mostrar errores en tiempo real.
- Almacenamiento en caché y carga lenta para evitar realizar solicitudes HTTP inútiles
- Ganchos de la máquina de estado (antes / después de guardar, actualizar, crear, nuevo, etc.)
Una biblioteca que hace todas estas cosas bien es ngActiveResource ( https://github.com/FacultyCreative/ngActiveResource ). Revelación completa: escribí esta biblioteca, y la he utilizado con éxito en la creación de varias aplicaciones de escala empresarial. Está bien probado y proporciona una API que debería ser 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 en la batalla.
DCI es un paradigma y, como tal, no hay una forma angular de hacerlo, ya sea el lenguaje compatible con DCI o no. JS soporta DCI bastante bien si está dispuesto a utilizar la transformación de origen y con algunos inconvenientes si no lo está. Nuevamente, DCI no tiene más que ver con la inyección de dependencia que decir que una clase C # tiene y definitivamente tampoco es un servicio. Entonces, la mejor manera de hacer DCI con angulusJS es hacer DCI a la manera JS, que es muy similar a cómo se formula DCI en primer lugar. A menos que realice la transformación de origen, no podrá hacerlo completamente, ya que los métodos de rol formarán parte del objeto incluso fuera del contexto, pero ese es generalmente el problema con el método basado en la inyección DCI. Si consulta fullOO.info, el sitio autorizado de DCI, puede consultar las implementaciones de Ruby que también utilizan el método de inyección o puede consultar here para obtener más información sobre DCI. Es sobre todo con ejemplos de RUby, pero las cosas de DCI son ajenas a 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, cuando se accede a un objeto a través de ese identificador, entonces 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, sino que está cerrado. 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);
};
}
Debería usar los servicios si quiere algo que puedan usar varios controladores. Aquí hay un simple ejemplo artificial:
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
}
Este artículo sobre modelos en AngularJS podría ayudar:
http://joelhooks.com/blog/2013/04/24/modeling-data-and-state-in-your-angularjs-application/
He tratado de abordar ese problema exacto en esta publicación de blog .
Básicamente, el mejor hogar para el modelado de datos es en servicios y fábricas. Sin embargo, dependiendo de cómo recupere sus datos y la complejidad de los comportamientos que necesita, hay muchas formas diferentes de llevar a cabo la implementación. Actualmente, Angular no tiene una forma estándar o la mejor práctica.
La publicación cubre tres enfoques, usando $ http , $ resource y Restangular .
Aquí hay un código de ejemplo para cada uno, con un getResult()
personalizado getResult()
en el modelo de trabajo:
Restangular (fácil peasy):
angular.module(''job.models'', [])
.service(''Job'', [''Restangular'', function(Restangular) {
var Job = Restangular.service(''jobs'');
Restangular.extendModel(''jobs'', function(model) {
model.getResult = function() {
if (this.status == ''complete'') {
if (this.passed === null) return "Finished";
else if (this.passed === true) return "Pass";
else if (this.passed === false) return "Fail";
}
else return "Running";
};
return model;
});
return Job;
}]);
$ recurso (un poco más complicado):
angular.module(''job.models'', [])
.factory(''Job'', [''$resource'', function($resource) {
var Job = $resource(''/api/jobs/:jobId'', { full: ''true'', jobId: ''@id'' }, {
query: {
method: ''GET'',
isArray: false,
transformResponse: function(data, header) {
var wrapped = angular.fromJson(data);
angular.forEach(wrapped.items, function(item, idx) {
wrapped.items[idx] = new Job(item);
});
return wrapped;
}
}
});
Job.prototype.getResult = function() {
if (this.status == ''complete'') {
if (this.passed === null) return "Finished";
else if (this.passed === true) return "Pass";
else if (this.passed === false) return "Fail";
}
else return "Running";
};
return Job;
}]);
$ http (hardcore):
angular.module(''job.models'', [])
.service(''JobManager'', [''$q'', ''$http'', ''Job'', function($q, $http, Job) {
return {
getAll: function(limit) {
var deferred = $q.defer();
$http.get(''/api/jobs?limit='' + limit + ''&full=true'').success(function(data) {
var jobs = [];
for (var i = 0; i < data.objects.length; i ++) {
jobs.push(new Job(data.objects[i]));
}
deferred.resolve(jobs);
});
return deferred.promise;
}
};
}])
.factory(''Job'', function() {
function Job(data) {
for (attr in data) {
if (data.hasOwnProperty(attr))
this[attr] = data[attr];
}
}
Job.prototype.getResult = function() {
if (this.status == ''complete'') {
if (this.passed === null) return "Finished";
else if (this.passed === true) return "Pass";
else if (this.passed === false) return "Fail";
}
else return "Running";
};
return Job;
});
La publicación del blog en sí explica más detalladamente los motivos por los que puede usar cada enfoque, así como ejemplos de código de cómo usar los modelos en sus controladores:
Modelos de datos AngularJS: $ http VS $ resource VS Restangular
Existe la posibilidad de que Angular 2.0 ofrezca una solución más robusta para el modelado de datos que haga que todos estén en la misma página.
La documentación de Angularjs establece claramente:
A diferencia de muchos otros marcos, Angular no impone restricciones ni requisitos al modelo. No hay clases para 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.
Entonces significa que depende de usted cómo declarar un modelo. Es un simple objeto de Javascript.
Personalmente, no utilizaré los Servicios Angulares, ya que estaban destinados a comportarse como objetos singleton que puede usar, por ejemplo, para mantener estados globales en su aplicación.
Una pregunta anterior, pero creo que el tema es más relevante que nunca dado la nueva dirección de Angular 2.0. Yo diría que una práctica recomendada es escribir código con la menor cantidad de dependencias posible en un marco particular. Utilice solo las partes específicas del marco donde agregue valor directo.
Actualmente parece que el servicio Angular es uno de los pocos conceptos que llegarán a la próxima generación de Angular, por lo que probablemente sea inteligente seguir la pauta general de mover toda la lógica a los servicios. Sin embargo, yo diría que se pueden hacer modelos desacoplados incluso sin una dependencia directa de los servicios de Angular. Crear objetos independientes con solo las dependencias y responsabilidades necesarias es probablemente el camino a seguir. También hace la vida mucho más fácil cuando se realizan pruebas automatizadas. La responsabilidad única es un trabajo de moda en estos días, ¡pero tiene mucho sentido!
Aquí hay un ejemplo de un patrón que considero bueno para desacoplar el modelo de objetos del dominio.
http://www.syntaxsuccess.com/viewarticle/548ebac8ecdac75c8a09d58e
Un objetivo clave es estructurar su código de manera que sea tan fácil de usar desde una prueba de unidad como desde una vista. Si logras eso, estás bien posicionado para escribir pruebas realistas y útiles.