Entwicklung einer AngularJS-App mit dynamischen Modulen

81

Ich habe eine Anwendung mit einem komplexen Layout, in der der Benutzer Widgets (per Drag & Drop) (durch Auswahl aus einem vordefinierten Satz von mehr als 100 Widgets) platzieren kann, wobei jedes Widget eine benutzerdefinierte Implementierung ist, die einen Datensatz anzeigt (mit REST-Aufruf abgerufen). auf eine bestimmte Weise. Ich habe unzählige Blog-Beiträge, Fragen zum Stackoverflow und die offiziellen AngularJS-Dokumente gelesen, kann aber nicht herausfinden, wie ich meine Anwendung so gestalten soll, dass sie den Anforderungen entspricht. Bei Demo-Apps gibt es ein einzelnes Modul (ng-app), und beim Erstellen in der .js-Datei werden die abhängigen Module als Abhängigkeiten deklariert. Ich habe jedoch eine große Anzahl von Widgets und es ist irgendwie nicht ratsam, sie alle zu beschreiben Dort. Ich brauche Vorschläge für die folgenden Fragen:

  • Wie soll ich meine App und Widgets gestalten - sollte ich ein separates AngularJS-Modul haben oder sollte jedes Widget eine Direktive zum Hauptmodul sein?
  • Wenn ich mein Widget als Direktiven entwerfe, gibt es eine Möglichkeit, die Abhängigkeit innerhalb einer Direktive zu definieren. Das heißt, dass meine Richtlinie bei ihrer Umsetzung ng-Kalender verwendet?
  • Wenn ich jedes Widget als separates Modul entwerfe, gibt es eine Möglichkeit, das Widget-Modul dynamisch als Abhängigkeit zum Hauptmodul hinzuzufügen?
  • Wie soll ich die Controller entwerfen - wahrscheinlich einen Controller pro Widget?
  • Wie soll ich den Status (Bereich) trennen, wenn ich mehrere Widgets desselben Typs in der Ansicht habe?
  • Gibt es Best Practices für das Entwerfen wiederverwendbarer Widgets mit AngularJS?

BEARBEITEN

Nützliche Referenzen:

Adrian Mitev
quelle
1
Mein erster Gedanke wäre, jedes Widget als Paar separater HTML / JS-Dateien zu erstellen, die über ng-include gerendert werden. Der Trick besteht darin, nur die erforderlichen Controller-JS-Dateien zu laden.
Shmiddty
Kann ich HTML dynamisch hinzufügen, das ng-include enthält?
Adrian Mitev
1
Ich glaube schon. In Ihrem Hauptcontroller hätten Sie eine Sammlung, die definiert, welche Widgets aktiv sind. Im Markup für die Sammlung hätten Sie ng-include an eine Eigenschaft gebunden, die auf die Ansicht HTML src des Widgets verweist. Ich bin mir ziemlich sicher, dass dies funktioniert, aber ich habe so etwas noch nicht gemacht.
Shmiddty

Antworten:

61

Dies sind nur allgemeine Ratschläge.

Wie soll ich meine App und Widgets gestalten - sollte ich ein separates AngularJS-Modul haben oder sollte jedes Widget eine Direktive zum Hauptmodul sein?

Sie sprechen von Hunderten von Widgets. Es scheint natürlich, sie in mehrere Module aufzuteilen. Einige Widgets haben möglicherweise mehr gemeinsam als andere Widgets. Einige sind möglicherweise sehr allgemein gehalten und passen in andere Projekte, andere sind spezifischer.

Wenn ich mein Widget als Direktiven entwerfe, gibt es eine Möglichkeit, die Abhängigkeit innerhalb einer Direktive zu definieren. Das heißt, dass meine Richtlinie bei ihrer Umsetzung ng-Kalender verwendet?

Abhängigkeiten zu anderen Modulen werden auf Modulebene vorgenommen, aber es gibt kein Problem, wenn das Modul Avom Modul Bund von beiden abhängt Aund Bvom Modul abhängt C. Direktiven sind eine natürliche Wahl für die Erstellung von Widgets in Angular. Wenn eine Direktive von einer anderen Direktive abhängt, definieren Sie sie entweder im selben Modul oder erstellen die Abhängigkeit auf modularer Ebene.

Wenn ich jedes Widget als separates Modul entwerfe, gibt es eine Möglichkeit, das Widget-Modul dynamisch als Abhängigkeit zum Hauptmodul hinzuzufügen?

Ich bin mir nicht sicher, warum Sie das tun möchten, und ich bin mir nicht sicher, wie ich es tun soll. Anweisungen und Dienste werden nicht initialisiert, bevor sie in Angular verwendet werden. Wenn Sie über eine große Bibliothek von Direktiven (Widgets) verfügen und wissen, dass Sie wahrscheinlich einige davon verwenden werden, aber nicht alle - aber Sie wissen nicht, welche verwendet werden, wenn die Anwendung initialisiert wird, können Sie tatsächlich "faul" sein Laden Sie "Ihre Anweisungen, nachdem Ihr Modul geladen wurde. Ich habe hier ein Beispiel erstellt

Der Vorteil ist, dass Sie Ihre Anwendung auch dann schnell laden können, wenn Sie viel Code haben, da Sie die Skripte nicht laden müssen, bevor Sie sie benötigen. Der Nachteil ist, dass es beim ersten Laden einer neuen Direktive zu einer erheblichen Verzögerung kommen kann.

Wie soll ich die Controller entwerfen - wahrscheinlich einen Controller pro Widget?

Ein Widget benötigt wahrscheinlich einen eigenen Controller. Controller sollten im Allgemeinen klein sein. Wenn sie groß werden, können Sie überlegen, ob es Funktionen gibt, die besser in einen Service passen.

Wie soll ich den Status (Bereich) trennen, wenn ich mehrere Widgets desselben Typs in der Ansicht habe?

Widgets, die Bereichsvariablen benötigen, sollten ohne Zweifel ihre eigenen isolierten Bereiche haben ( scope:{ ... }in der Direktivenkonfiguration).

Gibt es Best Practices für das Entwerfen wiederverwendbarer Widgets mit AngularJS?

Isolieren Sie den Bereich, halten Sie die Abhängigkeiten auf ein notwendiges Minimum. Sehen Sie sich Miskos Video über Best Practices in Angular an

Brian Ford hat auch einen Artikel über das Schreiben einer riesigen Anwendung in Angular geschrieben

Joakimbl
quelle
Vielen Dank für die tolle Antwort!
Adrian Mitev
17

Diese Frage ist mir auch sehr wichtig. Die AngularJS-Homepage enthält einige Beispiele (man könnte sie Widgets nennen), also habe ich ihren Quellcode durchgesehen, um zu sehen, wie sie ihre Widgets getrennt haben.

Erstens deklarieren sie niemals ein "ng-app" -Attribut. Sie benutzen

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']);
            });
      }
    }

um sicherzustellen, dass alles richtig geladen ist. Gute Idee, aber es ist seltsam, dass sie das ng-app-Attribut so sehr auf dich übertragen, dass sie es dann nicht einmal selbst verwenden. Wie auch immer, hier ist das Homepage-Modul, das sie mit der App laden - http://angularjs.org/js/homepage.js

Darin befindet sich eine Direktive namens 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);
      }
    };
  })

Ich werde die ToDo-Liste als Beispiel verwenden. Für das HTML haben sie

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

und dann am Ende der Seite, die sie haben

<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>

Sie haben auch

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

Der Code wird verwendet, aber die ID-Attribute in diesen Skripten sind für die Ausführung der App nicht wichtig. Dies ist nur für die Quellcode-Anzeige links von der App.

Grundsätzlich haben sie eine Direktive namens appRun, die eine Funktion fetchCode verwendet

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

um den Code abzurufen. Dann verwenden sie angle.bootstrap (), um eine neue Anwendung zu erstellen. Sie können Module auch über eine App laden. Das JavaScript-Projektbeispiel wird wie folgt initialisiert

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

Hoffentlich hilft das. Ich bin mir immer noch nicht sicher, was die "beste" Technik ist, aber es scheint, als ob die AngularJS-Homepage einfach eine völlig separate Winkelanwendung (ng-app) für jedes Beispiel / Widget verwendet. Ich denke, ich werde das Gleiche tun, außer die fetchCode-Funktion zu ändern, um Sachen mit AJAX zu bekommen.

beardedlinuxgeek
quelle
3
+1 dafür. Ich habe hier eine ähnliche Frage. Ich denke, Sie können möglicherweise helfen bei: stackoverflow.com/questions/17557088/…
Dan Kanze
Du hast mich in die richtige Richtung gelenkt. Für alle, die sich für die hasModule-Funktion interessieren, lasse ich meine Implementierung hier:function hasModule(name) { try { angular.module(name); } catch(err) { return false; } return true; }
Asier Paz