Tworzenie aplikacji AngularJS z dynamicznym zestawem modułów

80

Mam aplikację o złożonym układzie, w której użytkownik może umieścić (przeciągnij / upuść) widżety (wybierając z predefiniowanego zestawu ponad 100 widżetów), gdzie każdy widżet jest niestandardową implementacją, która wyświetla zestaw danych (pobieranych za pomocą wywołania REST) w określony sposób. Przeczytałem mnóstwo postów na blogu, pytań dotyczących stackoverflow i oficjalnej dokumentacji AngularJS, ale nie mogę dowiedzieć się, jak zaprojektować moją aplikację, aby sprostać tym wymaganiom. Patrząc na aplikacje demonstracyjne, jest pojedynczy moduł (ng-app) i podczas konstruowania go w pliku .js zależne moduły są zadeklarowane jako jego zależności, jednak mam duży zestaw widżetów i jakoś nie zaleca się ich wszystkich opisywać tam. Potrzebuję odpowiedzi na następujące pytania:

  • Jak zaprojektować swoją aplikację i widżety - czy powinienem mieć osobny moduł AngularJS, czy każdy widżet powinien być dyrektywą do modułu głównego?
  • Jeśli zaprojektuję widget jako dyrektywy, czy istnieje sposób na zdefiniowanie zależności w ramach dyrektywy. To znaczy, że moja dyrektywa używa ng-calender w swojej implementacji?
  • Jeśli zaprojektuję każdy widget jako oddzielny moduł, czy istnieje sposób na dynamiczne dodawanie modułu widgetu jako zależności do modułu głównego?
  • Jak zaprojektować kontrolery - prawdopodobnie jeden kontroler na widget?
  • Jak oddzielić stan (zakres), jeśli w widoku mam wiele widżetów tego samego typu?
  • Czy istnieją najlepsze praktyki projektowania widżetów wielokrotnego użytku w AngularJS?

EDYTOWAĆ

Przydatne referencje:

Adrian Mitev
źródło
1
Moją pierwszą myślą byłoby stworzenie każdego widżetu jako pary oddzielnych plików HTML / JS renderowanych za pomocą narzędzia ng-include. Sztuczka polega na załadowaniu tylko niezbędnych plików JS kontrolera.
Shmiddty
Mogę dynamicznie dodawać html, który zawiera ng-include?
Adrian Mitev
1
Tak mi się wydaje. W głównym kontrolerze miałbyś kolekcję, która definiuje, które widgety są aktywne, w znacznikach dla kolekcji miałbyś ng-include powiązany z jakąś właściwością, która wskazuje na src HTML widoku widżetu. Jestem prawie pewien, że to działa, ale tak naprawdę nie zrobiłem czegoś takiego.
Shmiddty

Odpowiedzi:

60

To tylko ogólne rady.

Jak zaprojektować swoją aplikację i widżety - czy powinienem mieć osobny moduł AngularJS, czy każdy widżet powinien być dyrektywą do modułu głównego?

Mówisz o setkach widżetów, wydaje się naturalne, że podzielisz je na kilka modułów. Niektóre widżety mogą mieć więcej wspólnego niż inne. Niektóre mogą być bardzo ogólne i pasować do innych projektów, inne są bardziej szczegółowe.

Jeśli zaprojektuję widget jako dyrektywy, czy istnieje sposób na zdefiniowanie zależności w ramach dyrektywy. To znaczy, że moja dyrektywa używa ng-calender w swojej implementacji?

Zależności od innych modułów są wykonywane na poziomie modułu, ale nie ma problemu, jeśli moduł Azależy od modułu Bi zarówno od modułu, jak Ai Bod niego C. Dyrektywy są naturalnym wyborem przy tworzeniu widżetów w Angular. Jeśli dyrektywa zależy od innej dyrektywy, definiujesz je w tym samym module lub tworzysz zależność na poziomie modułowym.

Jeśli zaprojektuję każdy widget jako oddzielny moduł, czy istnieje sposób na dynamiczne dodawanie modułu widgetu jako zależności do modułu głównego?

Nie jestem pewien, dlaczego chcesz to zrobić, i nie wiem, jak to zrobić. Dyrektywy i usługi nie są inicjowane, zanim zostaną użyte w Angular. Jeśli masz ogromną bibliotekę dyrektyw (widżetów) i wiesz, że prawdopodobnie użyjesz niektórych z nich, ale nie wszystkich - ale nie wiesz, które z nich zostaną użyte, gdy aplikacja zostanie zainicjowana, możesz w rzeczywistości "leniwy załaduj ”swoje dyrektywy po załadowaniu modułu. Stworzyłem przykład tutaj

Zaletą jest to, że możesz szybko ładować aplikację, nawet jeśli masz dużo kodu, ponieważ nie musisz ładować skryptów, zanim będziesz ich potrzebować. Wadą jest to, że przy pierwszym ładowaniu nowej dyrektywy może wystąpić znaczne opóźnienie.

Jak zaprojektować kontrolery - prawdopodobnie jeden kontroler na widget?

Widżet będzie prawdopodobnie potrzebował własnego kontrolera. Kontrolery powinny być generalnie małe, jeśli są duże, możesz rozważyć, czy istnieje jakakolwiek funkcjonalność, która lepiej pasowałaby do usługi.

Jak oddzielić stan (zakres), jeśli w widoku mam wiele widżetów tego samego typu?

Widżety, które wymagają zmiennych zakresu, powinny bez wątpienia mieć własne izolowane zakresy ( scope:{ ... }w konfiguracji dyrektywy).

Czy istnieją najlepsze praktyki projektowania widżetów wielokrotnego użytku w AngularJS?

Wyizoluj zakres, ogranicz zależności do niezbędnego minimum. Zobacz film Misko o najlepszych praktykach w Angular

Brian Ford napisał również artykuł o napisaniu ogromnej aplikacji w Angular

joakimbl
źródło
Dziękuję za niesamowitą odpowiedź!
Adrian Mitev
17

To pytanie jest również dla mnie bardzo ważne. Strona główna AngularJS zawiera kilka przykładów (można by je nazwać widżetami), więc przejrzałem ich kod źródłowy, aby sprawdzić, jak rozdzielili widżety.

Po pierwsze, nigdy nie deklarują atrybutu „ng-app”. Oni używają

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

aby upewnić się, że wszystko jest poprawnie załadowane. Niezły pomysł, ale to dziwne, że tak bardzo naciskają na atrybut ng-app, a nawet sami go nie używają. W każdym razie tutaj jest moduł strony głównej, którą ładują z aplikacją - http://angularjs.org/js/homepage.js

Jest tam dyrektywa o nazwie 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);
      }
    };
  })

Jako przykładu użyję listy rzeczy do zrobienia. W przypadku html mają

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

a następnie na dole strony, którą mają

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

Oni też mają

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

Kod jest używany, ale atrybuty id w tych skryptach nie są ważne dla uruchamiania aplikacji. Dotyczy to tylko wyświetlania kodu źródłowego po lewej stronie aplikacji.

Zasadniczo mają dyrektywę o nazwie appRun, która używa funkcji fetchCode

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

aby pobrać kod. Następnie używają angular.bootstrap () do tworzenia nowej aplikacji. Mogą również ładować moduły poprzez uruchamianie aplikacji. Przykład projektu JavaScript jest inicjowany jak

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

Mam nadzieję, że to pomoże. Nadal nie jestem pewien, jaka jest „najlepsza” technika, ale wygląda na to, że strona główna AngularJS po prostu używa całkowicie oddzielnej aplikacji kątowej (ng-app) dla każdego przykładu / widżetu. Myślę, że zrobię to samo, z wyjątkiem zmiany funkcji fetchCode, aby uzyskać rzeczy z AJAX.

beardedlinuxgeek
źródło
3
+1 za to. Mam tutaj podobne pytanie, myślę, że mógłbyś pomóc: stackoverflow.com/questions/17557088/ ...
Dan Kanze
wskazałeś mi we właściwym kierunku. Bo kto zainteresowany funkcją hasModule Zostawię realizację tutaj:function hasModule(name) { try { angular.module(name); } catch(err) { return false; } return true; }
Asier Paz