AngularJS: Comprender el patrón de diseño


En el contexto de este post por Igor Minar, líder de AngularJS:

MVC vs MVVM vs MVP. Lo que es un tema controvertido que muchos desarrolladores puede pasar horas y horas debatiendo y discutiendo.

Durante varios años AngularJS estuvo más cerca de MVC (o más bien uno de sus variantes del lado del cliente), pero con el tiempo y gracias a muchas refactorizaciones y las mejoras de la api, ahora está más cerca de MVVM - el $ámbito de aplicación objeto podría ser considerado el ViewModel que está siendo decorado por un función que llamamos un Controlador .

Ser capaz de categorizar un framework y ponerlo en uno de los buckets MV* tiene algunas ventajas. Puede ayudar a los desarrolladores a sentirse más cómodos con sus api al hacerlo más fácil crear un modelo mental que represente la aplicación que se está construyendo con el marco. También puede ayudar a establecer terminología utilizada por los desarrolladores.

Dicho esto, prefiero ver a los desarrolladores construir aplicaciones kick-ass que son bien diseñado y seguir la separación de las preocupaciones, que ver que los residuos tiempo discutiendo sobre MV * tonterías. Y por esta razón, declaro AngularJS to be MVW framework - Model-View-Whatever . Donde Lo que Sea significa " lo que funciona para ti".

Angular le da mucha flexibilidad para separar bien la presentación lógica de la lógica de negocio y el estado de presentación. Por favor, utilícelo combustible su productividad y capacidad de mantenimiento de la aplicación en lugar de calentarse discusiones sobre cosas que al final del día no importan mucho.

¿Hay alguna recomendación o guía para implementar el patrón de diseño de AngularJS MVW (Model-View-Whatever) en aplicaciones del lado del cliente?

Author: duplode, 2013-11-29

5 answers

Gracias a una gran cantidad de fuentes valiosas, tengo algunas recomendaciones generales para implementar componentes en aplicaciones AngularJS:


Controlador

  • El controlador debe ser solo una capa intermedia entre el modelo y la vista. Intenta que sea lo más delgado posible.

  • Es muy recomendable que evite la lógica de negocios en el controlador. Debe ser movido al modelo.

  • El Controlador puede comunicarse con otros controladores que usan invocación de métodos (posible cuando los hijos quieren comunicarse con el padre) o emit emit, $broadcast and on on methods. Los mensajes emitidos y emitidos deben reducirse al mínimo.

  • Al controlador no le importa la presentación o la manipulación del DOM.

  • Intente evitar los controladores anidados. En este caso, el controlador padre se interpreta como modelo. Inyectar modelos como servicios compartidos en su lugar.

  • Scope en el controlador debe usarse para binding model with view y
    encapsular Ver Modelocomo para Modelo de presentación patrón de diseño.


Ámbito de aplicación

Trate el ámbito como de solo lectura en las plantillasy de solo escritura en los controladores. El propósito del alcance es referirse al modelo, no ser el modelo.

Al hacer encuadernación bidireccional (ng-model) asegúrese de que no se vincule directamente a las propiedades del ámbito.


Modelo

El modelo en AngularJS es un singleton definido por service.

El modelo proporciona una excelente manera de separar los datos y la visualización.

Los modelos son candidatos principales para pruebas unitarias, ya que típicamente tienen exactamente una dependencia (alguna forma de emisor de eventos, en el caso común el ro rootScope) y contienen lógica de dominio altamente comprobable.

  • Modelo debe ser considerado como una implementación de unidad particular. Se basa en el principio de responsabilidad única. Unit es una instancia que es responsable de su propio alcance de lógica relacionada que puede representar una sola entidad en el mundo real y describirla en el mundo de programación en términos de datos y estado.

  • El modelo debe encapsular los datos de su aplicación y proporcionar una API para acceder y manipular esos datos.

  • El modelo debe ser portable so se puede transportar fácilmente a similares aplicación.

  • Al aislar la lógica de la unidad en su modelo, ha hecho que sea más fácil localizar, actualizar y mantener.

  • Modelo puede utilizar métodos de modelos globales más generales que son comunes para toda la aplicación.

  • Trate de evitar la composición de otros modelos en su modelo utilizando la inyección de dependencias si no es realmente dependiente para disminuir el acoplamiento de componentes y aumentar la unidad probabilidad y usabilidad.

  • Trate de evitar el uso de detectores de eventos en los modelos. Hace que sean más difíciles de probar y generalmente mata modelos en términos de principio de responsabilidad única.

Aplicación del modelo

Como el modelo debe encapsular alguna lógica en términos de datos y estado, debe restringir arquitectónicamente el acceso a sus miembros, por lo que podemos garantizar un acoplamiento suelto.

La forma de hacerlo en la aplicación AngularJS es definirlo usando tipo de servicio de fábrica . Esto nos permitirá definir propiedades privadas y métodos muy fáciles y también devolver los públicamente accesibles en un solo lugar que lo hará realmente legible para el desarrollador.

Un ejemplo:

angular.module('search')
.factory( 'searchModel', ['searchResource', function (searchResource) {

  var itemsPerPage = 10,
  currentPage = 1,
  totalPages = 0,
  allLoaded = false,
  searchQuery;

  function init(params) {
    itemsPerPage = params.itemsPerPage || itemsPerPage;
    searchQuery = params.substring || searchQuery;
  }

  function findItems(page, queryParams) {
    searchQuery = queryParams.substring || searchQuery;

    return searchResource.fetch(searchQuery, page, itemsPerPage).then( function (results) {
      totalPages = results.totalPages;
      currentPage = results.currentPage;
      allLoaded = totalPages <= currentPage;

      return results.list
    });
  }

  function findNext() {
    return findItems(currentPage + 1);
  }

  function isAllLoaded() {
    return allLoaded;
  }

  // return public model API  
  return {
    /**
     * @param {Object} params
     */
    init: init,

    /**
     * @param {Number} page
     * @param {Object} queryParams
     * @return {Object} promise
     */
    find: findItems,

    /**
     * @return {Boolean}
     */
    allLoaded: isAllLoaded,

    /**
     * @return {Object} promise
     */
    findNext: findNext
  };
});

Creando nuevas instancias

Trate de evitar tener una fábrica que devuelve una nueva función able ya que esto comienza a descomponer la inyección de dependencias y la biblioteca se comportará de manera incómoda, especialmente para terceros.

A la mejor manera de lograr lo mismo es usar la fábrica como una API para devolver una colección de objetos con los métodos getter y setter adjuntos a ellos.

angular.module('car')
 .factory( 'carModel', ['carResource', function (carResource) {

  function Car(data) {
    angular.extend(this, data);
  }

  Car.prototype = {
    save: function () {
      // TODO: strip irrelevant fields
      var carData = //...
      return carResource.save(carData);
    }
  };

  function getCarById ( id ) {
    return carResource.getById(id).then(function (data) {
      return new Car(data);
    });
  }

  // the public API
  return {
    // ...
    findById: getCarById
    // ...
  };
});

Modelo Global

En general, trate de evitar este tipo de situaciones y diseñar sus modelos correctamente por lo que se puede inyectar en el controlador y se utiliza en su vista.

En caso particular, algunos métodos requieren accesibilidad global dentro de la aplicación. Para hacerlo posible, puede definir la propiedad 'common ' en ro rootScope y enlazarlo a commonModel durante el bootstrap de la aplicación:

angular.module('app', ['app.common'])
.config(...)
.run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
  $rootScope.common = 'commonModel';
}]);

Todos tus métodos globales vivirán dentro de la propiedad ' common'. Esto es una especie de espacio de nombres .

Pero no defina ningún método directamente en su ro rootScope. Esto puede llevar a un comportamiento inesperado cuando se usa con la directiva ngModel dentro del alcance de la vista, generalmente ensuciando su alcance y conduce a la anulación de los métodos de alcance cuestión.


Recurso

Resource le permite interactuar con diferentes fuentes de datos .

Debe aplicarse utilizando el principio de responsabilidad única.

En particular, se trata de un proxy reutilizable para endpoints HTTP/JSON.

Los recursos se inyectan en los modelos y proporcionan la posibilidad de enviar/recuperar datos.

Implementación de recursos

Una fábrica que crea un objeto de recurso que le permite interactuar con Fuentes de datos RESTful del lado del servidor.

El objeto de recurso devuelto tiene métodos de acción que proporcionan comportamientos de alto nivel sin la necesidad de interactuar con el servicio http http de bajo nivel.


Servicios

Tanto el modelo como el recurso son servicios .

Los servicios no están asociados, unidades de funcionalidad poco acopladas que son autónomas.

Los servicios son una característica que Angular trae a las aplicaciones web del lado del cliente desde el lado del servidor, donde los servicios se han utilizado comúnmente durante mucho tiempo.

Los servicios de las aplicaciones de Angular son objetos sustituibles que se conectan mediante inyección de dependencias.

Angular viene con diferentes tipos de servicios. Cada uno con sus propios casos de uso. Por favor lea Descripción de los tipos de servicio para más detalles.

Trate de considerar los principios principales de la arquitectura de servicios en su aplicación.

En general según Servicios Web Glosario:

Un servicio es un recurso abstracto que representa una capacidad de realizar tareas que forman una funcionalidad coherente desde el punto de vista de entidades proveedores y entidades solicitantes. Para ser utilizado, a el servicio debe ser realizado por un agente proveedor concreto.


Estructura del lado del cliente

En general, el lado del cliente de la aplicación se divide en módulos . Cada módulo debe ser comprobable como unidad.

Intente definir módulos dependiendo de característica/funcionalidad o vista, no por tipo. Véase La presentación de Misko para más detalles.

Los componentes del módulo pueden agruparse convencionalmente por tipos como controladores, modelos, vistas, filtros, directivas, etc.

Pero módulo en sí sigue siendo reutilizables, transferible y comprobable.

También es mucho más fácil para los desarrolladores encontrar algunas partes del código y todo sus dependencias.

Por favor refiérase a Organización de código en AngularJS Grandes y Aplicaciones JavaScript para más detalles.

Un ejemplo de estructuración de carpetas :

|-- src/
|   |-- app/
|   |   |-- app.js
|   |   |-- home/
|   |   |   |-- home.js
|   |   |   |-- homeCtrl.js
|   |   |   |-- home.spec.js
|   |   |   |-- home.tpl.html
|   |   |   |-- home.less
|   |   |-- user/
|   |   |   |-- user.js
|   |   |   |-- userCtrl.js
|   |   |   |-- userModel.js
|   |   |   |-- userResource.js
|   |   |   |-- user.spec.js
|   |   |   |-- user.tpl.html
|   |   |   |-- user.less
|   |   |   |-- create/
|   |   |   |   |-- create.js
|   |   |   |   |-- createCtrl.js
|   |   |   |   |-- create.tpl.html
|   |-- common/
|   |   |-- authentication/
|   |   |   |-- authentication.js
|   |   |   |-- authenticationModel.js
|   |   |   |-- authenticationService.js
|   |-- assets/
|   |   |-- images/
|   |   |   |-- logo.png
|   |   |   |-- user/
|   |   |   |   |-- user-icon.png
|   |   |   |   |-- user-default-avatar.png
|   |-- index.html

Un buen ejemplo de estructuración de aplicaciones angular es implementado por angular-app - https://github.com/angular-app/angular-app/tree/master/client/src

Esto también es considerado por los generadores de aplicaciones modernas - https://github.com/yeoman/generator-angular/issues/109

 221
Author: Artem Platonov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:36

Creo que la opinión de Igor sobre esto, como se ve en la cita que ha proporcionado, es solo la punta del iceberg de un problema mucho mayor.

MVC y sus derivados (MVP, PM, MVVM) son todos buenos y dandy dentro de un solo agente, pero una arquitectura servidor-cliente es para todos los propósitos un sistema de dos agentes, y la gente a menudo está tan obsesionada con estos patrones que olvidan que el problema en cuestión es mucho más complejo. Al tratar de adherirse a estos principios que en realidad terminan con un arquitectura defectuosa.

Hagamos esto poco a poco.

Las directrices

Vistas

Dentro del contexto Angular, la vista es el DOM. Las directrices son:

Do:

  • Presente variable de alcance (solo lectura).
  • Llama al controlador para realizar acciones.

No:

  • Pon cualquier lógica.

Esto parece tentador, corto e inofensivo:{[12]]}

ng-click="collapsed = !collapsed"

Significa prácticamente cualquier desarrollador que ahora a comprender cómo funciona el sistema necesitan inspeccionar tanto los archivos Javascript como los HTML.

Controladores

Do:

  • Enlaza la vista con el 'modelo' colocando datos en el ámbito.
  • Responder a las acciones del usuario.
  • Trata con la lógica de presentación.

No:

  • Trata con cualquier lógica de negocio.

La razón de la última directriz es que los controladores son hermanas de las vistas, no de las entidades; reutilizable.

Se podría argumentar que las directivas son reutilizables, pero las directivas también son hermanas de las vistas (DOM) - nunca tuvieron la intención de corresponder a entidades.

Claro, a veces las vistas representan entidades, pero ese es un caso bastante específico.

En otras palabras, los controladores se centrarán en la presentación : si agrega lógica de negocios, no solo es probable que termine con un controlador inflado y poco manejable, sino que también viola la separación de principio de preocupación .

Como tal, los controladores en Angular son realmente más de Modelo de Presentación o MVVM .

Y entonces, si los controladores no deben lidiar con la lógica de negocios, ¿quién debería?

¿Qué es un modelo?

Su modelo de cliente es a menudo parcial y rancio

A menos que esté escribiendo una aplicación web fuera de línea, o una aplicación que sea terriblemente simple( pocas entidades), es muy probable que su modelo de cliente be:

  • Parcial
    • O bien no tiene todas las entidades (como en el caso de la paginación)
    • O no tiene todos los datos (como en el caso de la paginación)
  • Rancio - Si el sistema tiene más de un usuario, en cualquier momento no puede estar seguro de que el modelo que tiene el cliente es el mismo que el que tiene el servidor.

El modelo real debe persistir

En MCV tradicional, el modelo es el único el ser de la cosa persistió. Cada vez que hablamos de modelos, estos deben persistir en algún momento. Su cliente puede manipular modelos a voluntad, pero hasta que el viaje de ida y vuelta al servidor se completó con éxito, el trabajo no está hecho.

Consecuencias

Los dos puntos anteriores deben servir como una advertencia: el modelo que tiene su cliente solo puede involucrar una lógica de negocios parcial, en su mayoría simple.

Como tal, quizás sea prudente, dentro del contexto del cliente, usar M - así que es realmente mVC, mVP, y mVVm. El gran M es para el servidor.

Lógica de negocios

Quizás uno de los conceptos más importantes sobre los modelos de negocio es que puedes subdividirlos en 2 tipos (omito el tercero view-business ya que es una historia para otro día):

  • Domain logic - aka Enterprise business rules, la lógica que es independiente de la aplicación. Por ejemplo, dar un modelo con las propiedades firstName y sirName, un getter como getFullName() puede considerarse independiente de la aplicación.
  • Lógica de la aplicación - también conocida como Reglas de negocio de la aplicación, que es específica de la aplicación. Por ejemplo, comprobaciones y manejo de errores.

Es importante enfatizar que ambos dentro de un contexto de cliente son no lógica de negocio 'real' - solo tratan con la parte de la misma que es importante para el cliente. Lógica de la aplicación (no lógica del dominio) debe tener la responsabilidad de facilitar la comunicación con el servidor y la mayoría de la interacción del usuario; mientras que la lógica del dominio es en gran medida a pequeña escala, específica de la entidad y basada en la presentación.

La pregunta sigue siendo - ¿dónde se lanzan dentro de una aplicación angular?

Arquitectura de capas 3 vs 4

Todos estos frameworks MVW usan 3 capas:

Tres círculos. Modelo interno, controlador medio, vista exterior

Pero hay dos problemas fundamentales con esto cuando se trata de clientes:

  • El modelo es parcial, rancio y no persiste.
  • No hay lugar para poner la lógica de la aplicación.

Una alternativa a esta estrategia es la estrategia de 4 capas:

4 círculos, de interno a externo-Reglas de negocio de la empresa, reglas de negocio de la aplicación, Adaptadores de interfaz, marcos y controladores

El verdadero negocio aquí es la capa de reglas de negocio de aplicaciones (casos de uso), que a menudo va mal en los clientes.

Esta capa es realizada por interactors (Uncle Bob), que es más o menos lo que Martin Fowler llama un servicio de script de operación layer .

Ejemplo concreto

Considere la siguiente aplicación web:

  • La aplicación muestra una lista paginada de usuarios.
  • El usuario hace clic en 'Agregar usuario'.
  • Un modelo se abre con un formulario para rellenar los detalles del usuario.
  • El usuario rellena el formulario y pulsa enviar.

Algunas cosas deberían suceder ahora: {[12]]}

  • El formulario debe ser validado por el cliente.
  • Se enviará una solicitud al servidor.
  • Un error será manejado, si lo hay.
  • La lista de usuarios puede o no (debido a la paginación) necesita actualización.

¿Dónde lanzamos todo esto?

Si su arquitectura involucra a un controlador que llama a $resource, todo esto sucederá dentro del controlador. Pero hay una mejor estrategia.

Una solución propuesta

El siguiente diagrama muestra cómo se puede resolver el problema anterior agregando otra capa lógica de aplicación en Angular clientes:

4 cajas-DOM apunta al Controlador, que apunta a la lógica de la aplicación, que apunta a resource resource

Así que agregamos una capa entre el controlador a resource resource, esta capa (llamémoslo interactor):

  • Es un servicio . En el caso de los usuarios, se puede llamar UserInteractor.
  • Proporciona métodos correspondientes a casos de uso, encapsulando la lógica de la aplicación .
  • Controla las peticiones hechas al servidor. En lugar de un controlador que llama a resource resource con parámetros de forma libre, esto capa asegúrese de que las solicitudes realizadas al servidor devuelvan datos sobre los que puede actuar la lógica de dominio.
  • Decora la estructura de datos devuelta con prototipo de lógica de dominio.

Y así, con los requisitos del ejemplo concreto anterior:

  • El usuario hace clic en 'Agregar usuario'.
  • El controlador le pide al interactor un modelo de usuario en blanco, el está decorado con el método de lógica de negocios, como validate()
  • Tras el envío, el controlador llama al modelo validate() método.
  • Si falla, el controlador maneja el error.
  • Si tiene éxito, el controlador llama al interactor con createUser()
  • El interactor llama a resource resource
  • Al responder, el interactor delega cualquier error al controlador, que los maneja.
  • Tras una respuesta exitosa, el interactor se asegura de que, si es necesario, la lista de usuarios se actualice.
 46
Author: Izhaki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-02 21:59:43

Un problema menor en comparación con los grandes consejos en la respuesta de Artem, pero en términos de legibilidad del código, me pareció mejor definir la API completamente dentro del objeto return, para minimizar el ir y venir en el código para buscar las variables más definidas:

angular.module('myModule', [])
// or .constant instead of .value
.value('myConfig', {
  var1: value1,
  var2: value2
  ...
})
.factory('myFactory', function(myConfig) {
  ...preliminary work with myConfig...
  return {
    // comments
    myAPIproperty1: ...,
    ...
    myAPImethod1: function(arg1, ...) {
    ...
    }
  }
});

Si el objeto return se ve "demasiado abarrotado", eso es una señal de que el Servicio está haciendo demasiado.

 5
Author: Dmitri Zaitsev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-03 16:37:23

AngularJS no implementa MVC de manera tradicional, sino que implementa algo más cercano a MVVM(Model-View-ViewModel), ViewModel también se puede referir como binder(en caso angular puede ser scope scope). El modelo> > Como sabemos modelo en angular puede ser simplemente viejos objetos JS o los datos en nuestra aplicación

La vista ang > la vista en AngularJS es el HTML que ha sido analizado y compilado por AngularJS aplicando las directivas o instrucciones o enlaces, el punto principal aquí es en angular la entrada no es solo la cadena HTML simple(innerHTML), sino que es DOM creado por el navegador.

El ViewModel View> ViewModel es en realidad el binder/puente entre tu vista y el modelo en el caso AngularJS es scope scope, para inicializar y aumentar el Controller scope usamos el Controlador.

Si quiero resumir la respuesta: En la aplicación AngularJS scope scope hace referencia a los datos, el Controlador controla el comportamiento y View maneja el diseño interactuando con el controlador para comportarse consecuentemente.

 0
Author: Ashutosh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-04 08:02:03

Para ser nítidos sobre la pregunta, Angular utiliza diferentes patrones de diseño que ya encontramos en nuestra programación regular. 1) Cuando registramos nuestros controladores o directivas, fábrica, servicios, etc. con respecto a nuestro módulo. Aquí está escondiendo los datos del espacio global. Que es Module pattern . 2) Cuando angular usa su dirty checking para comparar las variables de ámbito, aquí usa Observer Pattern. 3) Todos los ámbitos padre hijo en nuestros controladores utiliza Patrón prototípico. 4) En caso de inyectar los servicios utiliza Patrón de fábrica.

En general, utiliza diferentes patrones de diseño conocidos para resolver los problemas.

 -1
Author: Naveen Reddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-08 12:35:52