Najlepsze praktyki AngularJS dotyczące deklaracji modułów?

115

Mam kilka modułów Angular zadeklarowanych w mojej aplikacji. Początkowo zacząłem je deklarować, używając składni „łańcuchowej” w następujący sposób:

angular.module('mymodule', [])
    .controller('myctrl', ['dep1', function(dep1){ ... }])
    .service('myservice', ['dep2', function(dep2){ ... }])
    ... // more here

Ale zdecydowałem, że nie jest to łatwe do odczytania, więc zacząłem je deklarować za pomocą zmiennej modułu, takiej jak ta:

var mod = angular.module('mymodule', []);

mod.controller('myctrl', ['dep1', function(dep1){ ... }]);

mod.service('myservice', ['dep2', function(dep2){ ... }]);
...

Druga składnia wydaje mi się dużo bardziej czytelna, ale moim jedynym zarzutem jest to, że ta składnia pozostawia modzmienną w zasięgu globalnym. Jeśli kiedykolwiek miałbym nazwaną inną zmienną mod, byłaby ona nadpisana następną (i innymi problemami związanymi ze zmiennymi globalnymi).

Więc moje pytanie brzmi: czy to najlepszy sposób? A może lepiej byłoby zrobić coś takiego ?:

(function(){
    var mod = angular.module('mymod', []);
    mod.controller('myctrl', ['dep1', function(dep1){ ... }]);
    mod.service('myservice', ['dep2', function(dep2){ ... }]);
    ...
})();

A może to w ogóle ma znaczenie, żeby się tym przejmować? Ciekawe, jakie są „najlepsze praktyki” dotyczące deklaracji modułów. Z góry dziękuję.

tenisista
źródło
3
To samo zastanawiałem się nad najlepszymi praktykami Angular
Dalorzo

Odpowiedzi:

118

„Najlepszy” sposób na zadeklarowanie modułu

Ponieważ angular jest w samym zasięgu globalnym, a moduły są zapisywane w jego zmiennej, dostęp do modułów można uzyskać za pośrednictwem angular.module('mymod'):

// one file
// NOTE: the immediately invoked function expression 
// is used to exemplify different files and is not required
(function(){
   // declaring the module in one file / anonymous function
   // (only pass a second parameter THIS ONE TIME as a redecleration creates bugs
   // which are very hard to dedect)
   angular.module('mymod', []);
})();


// another file and/or another anonymous function
(function(){   
 // using the function form of use-strict...
 "use strict";
  // accessing the module in another. 
  // this can be done by calling angular.module without the []-brackets
  angular.module('mymod')
    .controller('myctrl', ['dep1', function(dep1){
      //..
    }])

  // appending another service/controller/filter etc to the same module-call inside the same file
    .service('myservice', ['dep2', function(dep2){ 
    //... 
    }]);

  // you can of course use angular.module('mymod') here as well
  angular.module('mymod').controller('anothermyctrl', ['dep1', function(dep1){
      //..
  }])
})();

Żadne inne zmienne globalne nie są wymagane.

Oczywiście wszystko zależy od preferencji, ale myślę, że jest to najlepsza praktyka

  1. nie musisz zanieczyszczać globalnego zasięgu
  2. możesz uzyskać dostęp do swoich modułów w dowolnym miejscu i sortować je i ich funkcje w różnych plikach
  3. możesz użyć formy funkcji „użyj ścisłego”;
  4. kolejność ładowania plików nie ma tak dużego znaczenia

Opcje sortowania modułów i plików

Ten sposób deklarowania i uzyskiwania dostępu do modułów sprawia, że ​​jesteś bardzo elastyczny. Możesz sortować moduły według typu funkcji (jak opisano w innej odpowiedzi) lub według trasy, np .:

/******** sorting by route **********/    
angular.module('home')...
angular.module('another-route')...
angular.module('shared')...

Ostateczny sposób sortowania jest kwestią gustu oraz skali i rodzaju projektu. Osobiście lubię grupować wszystkie pliki modułu w tym samym folderze (uporządkowane w podfoldery dyrektyw, kontrolerów, usług i filtrów), w tym wszystkie różne pliki testowe, ponieważ sprawia to, że twoje moduły są bardziej wielokrotnego użytku. Tak więc w projektach średniej wielkości otrzymuję moduł podstawowy, który zawiera wszystkie podstawowe trasy i ich kontrolery, usługi, dyrektywy i mniej lub bardziej złożone podmoduły, kiedy myślę, że mogą być przydatne również w innych projektach, np. :

/******** modularizing feature-sets **********/
/controllers
/directives
/filters
/services
/my-map-sub-module
/my-map-sub-module/controllers
/my-map-sub-module/services
app.js
...

angular.module('app', [
  'app.directives',
  'app.filters',
  'app.controllers',
  'app.services',
  'myMapSubModule'
]);

angular.module('myMapSubModule',[
   'myMapSubModule.controllers',
   'myMapSubModule.services',
   // only if they are specific to the module
   'myMapSubModule.directives',
   'myMapSubModule.filters'
]);

W przypadku bardzo dużych projektów czasami kończę grupowanie modułów według tras, jak opisano powyżej lub według wybranych głównych tras, a nawet kombinacji tras i wybranych komponentów, ale to naprawdę zależy.

EDYCJA: Tylko dlatego, że jest to powiązane, a ostatnio znowu na to natknąłem: Uważaj, aby utworzyć moduł tylko raz (dodając drugi parametr do funkcji angular.module). Spowoduje to zepsucie aplikacji i może być bardzo trudne do wykrycia.

EDYCJA 2015 dotycząca sortowania modułów: Półtora roku późniejszych doświadczeń z kątami, mogę dodać, że korzyści wynikające z używania modułów o różnych nazwach w Twojej aplikacji są nieco ograniczone, ponieważ AMD nadal nie działa dobrze z Angular i usługami, dyrektywami i filtrami i tak są globalnie dostępne w kontekście kątowym ( jak pokazano tutaj na przykładzie ). Jednak nadal istnieje semantyczna i strukturalna korzyść i może być pomocne włączenie / wyłączenie modułu z jedną linią kodu skomentowaną lub usuniętą.

Również prawie nigdy nie ma sensu rozdzielanie podmodułów według typu (np. „MyMapSubModule.controllers”), ponieważ zwykle są one od siebie zależne.

hugo der hungrige
źródło
7
Nie potrzebujesz IIFE (natychmiastowo wywoływanego wyrażenia funkcji), czyli anonimowej, samowykonującej się funkcji
plus
1
Masz rację. Potrzebujesz go tylko wtedy, gdy chcesz zastosować formę funkcji „użyj ścisłego”; Ale to nie boli.
hugo der hungrige
1
W większości przypadków można również umieścić 'use strict';wewnątrz komponentu. module.controller(function () { 'use strict'; ... });
Jackson,
Lubię wdrażanie, ale nie lubię też łączyć łańcuchów, więc mieszam to z tym, co robi Beterraba
Mirko
1
w przypadku AMD wystarczy jeden moduł o nazwie app. Można wykluczyć moduł AMD, usuwając wymaganie. Nie musimy już rejestrować kontrolerów i usług.
James
28

Uwielbiam przewodnik po stylach kątowych autorstwa Johnpapy, a oto kilka zasad związanych z tym pytaniem:

Reguła: Funkcje nazwane a funkcje anonimowe

Unikaj korzystania z funkcji anonimowych:

// dashboard.js
angular
  .module('app')
  .controller('Dashboard', function() { })

Zamiast tego użyj nazwanych funkcji:

// dashboard.js
angular
  .module('app')
  .controller('Dashboard', Dashboard);

function Dashboard() { }

Jak mówi autor: This produces more readable code, is much easier to debug, and reduces the amount of nested callback code.

Reguła: zdefiniuj 1 komponent na plik.

Unikaj wielu komponentów w jednym pliku:

angular
  .module('app', ['ngRoute'])
  .controller('SomeController', SomeController)
  .factory('someFactory', someFactory);

function SomeController() { }

function someFactory() { }

Intead, użyj jednego pliku do zdefiniowania modułu:

// app.module.js
angular
  .module('app', ['ngRoute']);

jeden plik po prostu używa modułu do zdefiniowania komponentu

// someController.js
angular
  .module('app')
  .controller('SomeController', SomeController);

function SomeController() { }

i inny plik do zdefiniowania innego komponentu

// someFactory.js
angular
  .module('app')
  .factory('someFactory', someFactory);

function someFactory() { }

Oczywiście istnieje wiele innych zasad dotyczących modułów, kontrolerów i usług, które są całkiem przydatne i warte przeczytania.

A dzięki komentarzowi ya_dimon powyższy kod powinien być opakowany w IIFE, na przykład:

(function (window, angular) {
  angular.module('app')
   .controller('Dashboard', function () { });
})(window, window.angular);
aqingsao
źródło
Dobra odpowiedź i świetny link.
Ellesedil
jeśli mam różne kontrolery w różnych plikach javascript, czy nie będzie to wymagało załadowania większej liczby plików, czyli większej liczby trafień na serwer?
Vignesh Subramanian
Dość łatwo jest je scalić / uszlachetnić / zmienić ich nazwę jednym haustem lub chrząknięciem, vignesh i osobiście uwielbiam łyk.
aqingsao
1
zapomniałeś dodać, że wszystkie te fragmenty powinny być w IIFE, w przeciwnym razie masz globalnie funkcje takie jak "someFactory ()". Istnieje szansa na kolizje nazw. (i nie potrzebujesz IIFE w es6)
ya_dimon
12

Niedawno też miałem tę zagadkę. Zacząłem tak jak ty, używając połączonej składni, ale na dłuższą metę staje się ona nieporęczna w przypadku dużych projektów. Zwykle tworzyłem moduł kontrolerów, moduł usług i tak dalej w oddzielnych plikach i wstawiam je do mojego głównego modułu aplikacji znajdującego się w innym pliku. Na przykład:

// My Controllers File
angular.module('my-controllers',[])
    .controller('oneCtrl',[...])
    .controller('twoCtrl',[...]);

// My Services File
angular.module('my-services',[])
    .factory('oneSrc',[...])
    .facotry('twoSrc',[...]);

// My Directives File
angular.module('my-directives',[])
    .directive('oneDrct',[...])
    .directive('twoDrct',[...]);

// My Main Application File
angular.module('my-app',['my-controllers','my-services','my-directives',...]);

Ale każdy z tych plików stawał się coraz większy w miarę rozwoju projektu. Postanowiłem więc podzielić je na osobne pliki na podstawie każdego kontrolera lub usługi. Odkryłem, że użycie angular.module('mod-name').bez tablicy wtrysku jest tym, czego potrzebujesz, aby to zadziałało. Zadeklarowanie zmiennej globalnej w jednym pliku i oczekiwanie, że będzie ona łatwo dostępna w innym, po prostu nie działa lub może mieć nieoczekiwane rezultaty.

Więc w skrócie moja aplikacja wyglądała mniej więcej tak:

// Main Controller File
angular.module('my-controllers',[]);

// Controller One File
angular.module('my-controllers').controller('oneCtrl',[...]);

//Controller Two File
angular.module('my-controllers').controller('twoCtrl',[...]);

Zrobiłem to również w pliku usług, nie trzeba zmieniać głównego pliku modułu aplikacji, do którego nadal będziesz wstrzykiwać te same moduły.

meconroy
źródło
1
jaki jest sens tworzenia oddzielnych modułów dla usług / dyrektyw / kontrolerów?
Filip Sobczak
2
W dużych projektach sytuacja może być trudna do znalezienia, gdy kontrolery / filtry / dyrektywy / usługi są przeplatane razem. To tylko jeden sposób na utrzymanie porządku.
meconroy
1
@FilipSobczak NIE tworzy osobnych modułów dla usług / dyrektyw / kontrolerów. Zamiast tego utworzył moduł tylko raz, używając angular.module('my-controllers',[]);(Zauważ, że określa [] tylko raz dla deklaracji). Po prostu wykorzystuje to ponownie w innych plikach. Separacja plików sprawia, że ​​utrzymanie projektu, zwłaszcza dużych, jest stosunkowo łatwe.
Devner,
8

Inną praktyką jest umieszczanie kontrolerów, dyrektyw itp. W ich własnych modułach i wstawianie tych modułów do "głównego":

angular.module('app.controllers', [])
  .controller('controller1', ['$scope', function (scope) {
    scope.name = "USER!";
  }]);

angular.module('app.directives', [])
  .directive('myDirective', [function () {
    return {
      restrict: 'A',
      template: '<div>my directive!</div>'
    }
  }]);

angular.module('app', [
  'app.controllers',
  'app.directives'
]);

Nic nie pozostaje w zasięgu globalnym.

http://plnkr.co/edit/EtzzPRyxWT1MkhK7KcLo?p=preview

Manny D.
źródło
Dlaczego używasz app.controllersinsted of controllersjako nazwy modułu, czy jest jakaś korzyść? Jestem nowicjuszem w Angularjs
sijo vijayan
4

Lubię dzielić moje pliki i moje moduły.

Coś takiego:

app.js

var myApp = angular.module('myApp', ['myApp.controllers', 'myApp.directives', 'myApp.services']);

myApp.config(['$routeProvider', function($routeProvider) {
    /* routes configs */
    $routeProvider.when(/*...*/);
}]);

directives.js

var myDirectives = angular.module('myApp.directives', []);

myDirectives.directive( /* ... */ );

service.js

var myServices = angular.module('myApp.services', []);

myServices.factory( /* ... */ );

Nie jestem wielkim fanem „stylu łańcuchowego”, więc wolę zawsze zapisywać swoją zmienną.

Beterraba
źródło
2
To jest sposób, w jaki to robiłem, ale każdy plik services.js lub controller.js szybko się rozwija w projekcie na dużą skalę, ostatecznie będziesz musiał podzielić każdą usługę lub kontroler na osobny plik.
meconroy
1
@meconroy Exactly. Kiedy rzecz staje się coraz większa, lubię podzielić dyrektywę na mniejsze moduły i wstrzyknąć ją do "głównego" modułu dyrektywy.
Beterraba
0

Dla mnie tworzenie łańcuchów to najbardziej kompaktowy sposób:

angular.module("mod1",["mod1.submod1"])

 .value("myValues", {
   ...
 })

 .factory("myFactory", function(myValues){
   ...
 })

 .controller("MainCtrl", function($scope){

   // when using "Ctrl as" syntax
   var MC = this;
   MC.data = ...;
 })
 ;

W ten sposób mogę łatwo przenosić komponenty między modułami, nigdy nie muszę dwukrotnie deklarować tego samego modułu, nigdy nie potrzebuję żadnych zmiennych globalnych.

A jeśli plik stanie się zbyt długi, rozwiązanie jest proste - podziel na dwa pliki, z których każdy deklaruje swój własny moduł u góry. Dla większej przejrzystości staram się zachować jeden unikalny moduł na plik i nazwać go tak, aby przypominał pełną ścieżkę do pliku. W ten sposób nigdy nie muszę pisać modułu bez [], co jest częstym problemem.

Dmitri Zaitsev
źródło