Desarrollando una aplicación AngularJS con un conjunto dinámico de módulos

81

Tengo una aplicación con un diseño complejo donde el usuario puede colocar (arrastrar / soltar) widgets (eligiendo entre un conjunto predefinido de más de 100 widgets) donde cada widget es una implementación personalizada que muestra un conjunto de datos (obtenido mediante una llamada REST) de una manera específica. He leído toneladas de publicaciones de blog, preguntas de stackoverflow y los documentos oficiales de AngularJS, pero no puedo entender cómo debo diseñar mi aplicación para manejar sus requisitos. En cuanto a las aplicaciones de demostración, hay un solo módulo (ng-app) y al construirlo en el archivo .js, los módulos dependientes se declaran como sus dependencias, sin embargo, tengo un gran conjunto de widgets y de alguna manera no es recomendable describirlos a todos. ahí. Necesito sugerencias para las siguientes preguntas:

  • ¿Cómo debo diseñar mi aplicación y widgets? ¿Debo tener un módulo AngularJS separado o cada widget debe ser una directiva para el módulo principal?
  • Si diseño mi widget como directivas, ¿hay alguna forma de definir la dependencia dentro de una directiva? Es decir, ¿mi directiva usa ng-calender en su implementación?
  • Si diseño cada widget como un módulo separado, ¿hay alguna forma de agregar dinámicamente el módulo del widget como una dependencia del módulo principal?
  • ¿Cómo debo diseñar los controladores, probablemente un controlador por widget?
  • ¿Cómo debo separar el estado (alcance) si tengo varios widgets del mismo tipo en la vista?
  • ¿Existen las mejores prácticas para diseñar widgets reutilizables con AngularJS?

EDITAR

Referencias útiles:

Adrian Mitev
fuente
1
Mi primer pensamiento sería crear cada widget como un par de archivos HTML / JS separados, renderizados a través de ng-include. El truco consiste en cargar solo los archivos JS del controlador necesarios.
Shmiddty
¿Puedo agregar dinámicamente html que contenga ng-include?
Adrian Mitev
1
Eso creo. En su controlador principal, tendría una colección que define qué widgets están activos, en el marcado de la colección, tendría ng-include vinculado a alguna propiedad que apunta a la vista HTML src de dicho widget. Estoy bastante seguro de que esto funciona, pero en realidad no he hecho algo como esto.
Shmiddty

Respuestas:

61

Estos son solo consejos generales.

¿Cómo debo diseñar mi aplicación y widgets? ¿Debo tener un módulo AngularJS separado o cada widget debe ser una directiva para el módulo principal?

Estás hablando de cientos de widgets, parece natural dividirlos en varios módulos. Algunos widgets pueden tener más en común que otros widgets. Algunos pueden ser muy generales y encajar en otros proyectos, otros son más específicos.

Si diseño mi widget como directivas, ¿hay alguna forma de definir la dependencia dentro de una directiva? Es decir, ¿mi directiva usa ng-calender en su implementación?

Las dependencias de otros módulos se realizan a nivel de módulo, pero no hay problema si el módulo Adepende del módulo By ambos Ay Bdepende del módulo C. Las directivas son una opción natural para crear widgets en Angular. Si una directiva depende de otra directiva, la defina en el mismo módulo o cree la dependencia en un nivel modular.

Si diseño cada widget como un módulo separado, ¿hay alguna forma de agregar dinámicamente el módulo del widget como una dependencia del módulo principal?

No estoy seguro de por qué querría hacer esto y no estoy seguro de cómo hacerlo. Las directivas y los servicios no se inicializan antes de que se usen en Angular. Si tiene una gran biblioteca de directivas (widgets) y sabe que probablemente usará algunas de ellas, pero no todas, pero no sabe cuáles se usarán cuando se inicialice la aplicación, puede "perezoso" cargar "sus directivas después de que se haya cargado su módulo. He creado un ejemplo aquí

El beneficio es que puede hacer que su aplicación se cargue rápidamente incluso si tiene mucho código, porque no tiene que cargar los scripts antes de que los necesite. La desventaja es que puede haber un retraso considerable la primera vez que se carga una nueva directiva.

¿Cómo debo diseñar los controladores, probablemente un controlador por widget?

Un widget probablemente necesitará su propio controlador. Los controladores generalmente deben ser pequeños, si crecen, puede considerar si hay alguna funcionalidad que se adapte mejor a un servicio.

¿Cómo debo separar el estado (alcance) si tengo varios widgets del mismo tipo en la vista?

Los widgets que necesitan variables de ámbito deberían, sin duda, tener sus propios ámbitos aislados ( scope:{ ... }en la configuración de la directiva).

¿Existen las mejores prácticas para diseñar widgets reutilizables con AngularJS?

Aísle el alcance, mantenga las dependencias al mínimo necesario. Vea el video de Misko sobre las mejores prácticas en Angular

Brian Ford también ha escrito un artículo sobre cómo escribir una gran aplicación en Angular

joakimbl
fuente
¡Gracias por la increíble respuesta!
Adrian Mitev
17

Esta pregunta también es muy importante para mí. La página de inicio de AngularJS tiene algunos ejemplos (podría llamarlos widgets), así que revisé su código fuente para tratar de ver cómo separaban sus widgets.

Primero, nunca declaran un atributo "ng-app". Ellos usan

function bootstrap() {
      if (window.prettyPrint && window.$ && $.fn.popover && angular.bootstrap &&
          hasModule('ngLocal.sk') && hasModule('ngLocal.us') && hasModule('homepage') && hasModule('ngResource')) {
            $(function(){
              angular.bootstrap(document, ['homepage', 'ngLocal.us']);
            });
      }
    }

para asegurarse de que todo esté cargado correctamente. Buena idea, pero es extraño que te impongan tanto el atributo ng-app que ni siquiera lo usen ellos mismos. De todos modos, aquí está el módulo de la página de inicio que cargan con la aplicación: http://angularjs.org/js/homepage.js

Hay una directiva llamada appRun

  .directive('appRun', function(fetchCode, $templateCache, $browser) {
    return {
      terminal: true,
      link: function(scope, element, attrs) {
        var modules = [];

        modules.push(function($provide, $locationProvider) {
          $provide.value('$templateCache', {
            get: function(key) {
              var value = $templateCache.get(key);
              if (value) {
                value = value.replace(/\#\//mg, '/');
              }
              return value;
            }
          });
          $provide.value('$anchorScroll', angular.noop);
          $provide.value('$browser', $browser);
          $locationProvider.html5Mode(true);
          $locationProvider.hashPrefix('!');
        });
        if (attrs.module) {
          modules.push(attrs.module);
        }

        element.html(fetchCode(attrs.appRun));
        element.bind('click', function(event) {
          if (event.target.attributes.getNamedItem('ng-click')) {
            event.preventDefault();
          }
        });
        angular.bootstrap(element, modules);
      }
    };
  })

Usaré la lista de tareas pendientes como ejemplo. Para el html, tienen

<div app-run="todo.html" class="well"></div>

y luego en la parte inferior de la página tienen

<script type="text/ng-template" id="todo.html">
  <h2>Todo</h2>
  <div ng-controller="TodoCtrl">
    <span>{{remaining()}} of {{todos.length}} remaining</span>
    [ <a href="" ng-click="archive()">archive</a> ]
    <ul class="unstyled">
      <li ng-repeat="todo in todos">
        <input type="checkbox" ng-model="todo.done">
        <span class="done-{{todo.done}}">{{todo.text}}</span>
      </li>
    </ul>
    <form ng-submit="addTodo()">
      <input type="text" ng-model="todoText"  size="30"
             placeholder="add new todo here">
      <input class="btn-primary" type="submit" value="add">
    </form>
  </div>
</script>

También tienen

<style type="text/css" id="todo.css"> //style stuff here </style>
<script id="todo.js"> //controller stuff here </script>

Se usa el código, pero los atributos de identificación en esos scripts no son importantes para ejecutar la aplicación. Eso es solo para la visualización del código fuente a la izquierda de la aplicación.

Básicamente, tienen una directiva llamada appRun que usa una función fetchCode

  .factory('fetchCode', function(indent) {
    return function get(id, spaces) {
      return indent(angular.element(document.getElementById(id)).html(), spaces);
    }
  })

para recuperar el código. Luego usan angular.bootstrap () para crear una nueva aplicación. También pueden cargar módulos mediante la ejecución de la aplicación. El ejemplo del proyecto JavaScript se inicializa como

<div app-run="project.html" module="project" class="well"></div>

Ojalá esto ayude. Todavía no estoy seguro de cuál es la "mejor" técnica, pero parece que la página de inicio de AngularJS simplemente usa una aplicación angular totalmente separada (ng-app) para cada ejemplo / widget. Creo que voy a hacer lo mismo, excepto cambiar la función fetchCode para obtener cosas con AJAX.

beardedlinuxgeek
fuente
3
+1 para esto. Tengo una pregunta similar aquí, creo que podría ayudarme con: stackoverflow.com/questions/17557088/…
Dan Kanze
me indicaste en la dirección correcta. Para quien esté interesado en la función hasModule les dejo mi implementación aquí:function hasModule(name) { try { angular.module(name); } catch(err) { return false; } return true; }
Asier Paz