AngularJS: zrozumienie wzorca projektowego

W kontekście tego postu autorstwa Igora Minara, lidera AngularJS:

MVC vs MVVM vs MVP . Co za kontrowersyjny temat, że wielu programistów może spędzać wiele godzin na debatach i kłótniach.

Przez kilka lat AngularJS był bliżej MVC (a raczej jednego z jego warianty po stronie klienta), ale z czasem i dzięki wielu refaktoringom i ulepszeń api, jest teraz bliżej MVVM – the $zakres obiekt może być uważane za ViewModel , który jest dekorowany przez funkcja, którą nazywamy kontrolerem .

Możliwość kategoryzowania frameworka i umieszczania go w jednym z wiadra MV* ma pewne zalety. Może pomóc programistom uzyskać więcej komfortu z jego interfejsów API, czyniąc go łatwiej stworzyć model mentalny, który reprezentuje aplikację, która jest budowany w oparciu o ramy. Może również pomóc w ustaleniu terminologia używana przez programistów.

Powiedziawszy, wolałbym widzieć deweloperów budujących zajebiste aplikacje, które są dobrze zaprojektowane i po rozdzieleniu obaw, niż zobaczyć je odpady czas kłócić się o MV * nonsens. I z tego powodu Niniejszym oświadczam AngularJS To be MVW framework-Model-View-Whatever . Where Whatever oznacza " cokolwiek działa dla Ciebie ".

Angular daje dużą elastyczność, aby ładnie oddzielić prezentację logika z logiki biznesowej i stanu prezentacji. Proszę użyć paliwa twoja Produktywność i łatwość konserwacji aplikacji zamiast podgrzewania Dyskusje o rzeczach, które na koniec dnia nie mają znaczenia, że much.

Czy są jakieś zalecenia lub wytyczne dotyczące implementacji wzorca projektowego AngularJS MVW (Model-View-Whatever) w aplikacjach po stronie klienta?

Author: duplode, 2013-11-29

5 answers

Dzięki ogromnej ilości cennych źródeł mam kilka ogólnych zaleceń dotyczących implementacji komponentów w aplikacjach AngularJS:


Controller

  • Kontroler powinien być tylko międzywarstwą pomiędzy modelem a widokiem. Postaraj się, aby był jak cienki , Jak to możliwe.

  • Zaleca się, aby unikać logiki biznesowej w kontrolerze. Należy go przenieść do modelu.

  • Kontroler może komunikować się z innymi Kontrolery wykorzystujące wywołanie metody (możliwe, gdy dzieci chcą komunikować się z rodzicem) lub $emit, $broadcast i $on metody. Emitowane i nadawane komunikaty powinny być ograniczone do minimum.

  • Kontroler powinien nie przejmować się prezentacją ani manipulacją DOM.

  • Spróbuj unikać zagnieżdżonych kontrolerów. W tym przypadku kontroler nadrzędny jest interpretowany jako model. Inject models as shared services zamiast tego.

  • Zakres w kontrolerze powinien być używany do wiązania modelu z widokiem i
    enkapsulacja model widoku Jak dla Model prezentacji wzór projektowy.


Zakres

Traktuj scope jako Tylko do odczytu w szablonach i Tylko do zapisu w kontrolerach. Celem zakresu jest odniesienie się do modelu, a nie do modelu.

Wykonując Wiązanie dwukierunkowe (model ng) upewnij się, że nie wiąże się bezpośrednio z właściwościami zakresu.


Model

Model w AngularJS tosingleton zdefiniowany przezserwis .

Model zapewnia doskonały sposób na oddzielenie danych i wyświetlanie.

Modele są głównymi kandydatami do testów jednostkowych, ponieważ zazwyczaj mają dokładnie jedną zależność (jakąś formę emitera zdarzeń, w powszechnym przypadku $rootScope ) i zawierają wysoce testowalną logikę domeny.

  • Model powinien być rozpatrywane jako realizacja danej jednostki. Opiera się ona na zasadzie jednej odpowiedzialności. Jednostka jest instancją odpowiedzialną za własny zakres logiki, która może reprezentować pojedynczą jednostkę w świecie rzeczywistym i opisywać ją w świecie programowania w kategoriach danych i stanu .

  • Model powinien zawierać dane aplikacji i dostarczać API aby uzyskać dostęp i manipulować tymi danymi.

  • Model powinien być przenośny więc można go łatwo przetransportować do podobnych podanie.

  • Izolując logikę jednostki w swoim modelu ułatwiłeś zlokalizuj, Aktualizuj i utrzymuj.

  • Model może używać metod bardziej ogólnych modeli globalnych, które są powszechne dla całej aplikacji.

  • Staraj się unikać komponowania innych modeli do swojego modelu za pomocą iniekcji zależności, Jeśli nie jest to tak naprawdę zależne od zmniejszenia sprzężenia składników i zwiększenia jednostki testowalność i usability .

  • Staraj się unikać używania słuchaczy zdarzeń w modelach. Utrudnia to ich testowanie i ogólnie zabija modele pod względem zasady jednej odpowiedzialności.

Implementacja Modelu

Ponieważ model powinien zawierać pewną logikę pod względem danych i stanu, powinien architektonicznie ograniczać dostęp do swoich członków, dzięki czemu możemy zagwarantować luźne sprzężenie.

Sposobem na to w aplikacji AngularJS jest zdefiniowanie go za pomocą fabryka rodzaj usługi. Pozwoli nam to bardzo łatwo definiować prywatne właściwości i metody, a także zwracać publicznie dostępne w jednym miejscu, co sprawi, że będzie naprawdę czytelne dla programistów.

Przykład :

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

Tworzenie nowych instancji

Staraj się unikać posiadania fabryki, która zwraca nową funkcję able, ponieważ zaczyna się to rozkładać Wtrysk zależności i biblioteka będzie zachowywać się niezręcznie, szczególnie dla osób trzecich.

A lepszym sposobem na osiągnięcie tego samego jest użycie factory jako API do zwrócenia kolekcji obiektów z dołączonymi do nich metodami getter i setter.

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
    // ...
  };
});

Model Globalny

Ogólnie staraj się unikać takich sytuacji i odpowiednio Zaprojektuj swoje modele, aby mogły być wtryskiwane do kontrolera i używane według Ciebie.

W szczególności niektóre metody wymagają globalnej dostępności w aplikacji. Aby było to możliwe można zdefiniować właściwość' common ' w $rootScope i powiązać go z commonModel podczas uruchamiania aplikacji:

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

Wszystkie Twoje globalne metody będą żyć w ' common' właściwość. Jest to rodzaj przestrzeni nazw.

Ale nie Definiuj żadnych metod bezpośrednio w $rootScope. Może to prowadzić do nieoczekiwanego zachowania W przypadku użycia z dyrektywą ngModel w Twoim zakresie widzenia, zazwyczaj zaśmiecając twój zakres i doprowadzając do nadpisania metod zakresu problemy.


Zasób

Zasób pozwala na interakcję z różnymi źródłami danych .

Powinny być realizowane przy użyciu Zasady pojedynczej odpowiedzialności .

W szczególnym przypadku jest to wielokrotnego użytku proxy do punktów końcowych HTTP/JSON.

Zasoby są wtryskiwane w modelach i zapewniają możliwość wysyłania / pobierania danych.

Implementacja zasobów

Fabryka, która tworzy obiekt zasobu, który pozwala na interakcję z RESTful źródła danych po stronie serwera.

Zwracany obiekt zasobu posiada metody akcji, które zapewniają zachowania wysokiego poziomu bez konieczności interakcji z usługą $http niskiego poziomu.


Usługi

Zarówno model jak i zasób to usługi .

Usługi są niezwiązane, luźno powiązane jednostki funkcjonalności, które są autonomiczne.

Usługi to funkcja, którą Angular przenosi do aplikacji internetowych po stronie klienta od strony serwera, gdzie usługi są powszechnie używane przez długi czas.

Usługi w aplikacjach Angular są obiektami zastępowalnymi, które są połączone ze sobą za pomocą iniekcji zależności.

Angular oferuje różne rodzaje usług. Każdy z nich ma swoje własne przypadki użycia. Proszę przeczytać zrozumienie typów usług aby uzyskać szczegółowe informacje.

Spróbuj rozważyć główne zasady architektury usług w swojej aplikacji.

Ogólnie według serwisów internetowych Słowniczek :

Usługa jest abstrakcyjnym zasobem, który reprezentuje zdolność wykonywanie zadań, które tworzą spójną funkcjonalność z punktu widok podmiotów oferujących i podmiotów zamawiających. Do wykorzystania, a usługa musi być realizowana przez konkretnego agenta dostawcy.


Struktura po stronie Klienta

Ogólnie strona klienta aplikacji jest podzielona na moduły . Każdy moduł powinien być testowalny jako unit.

Spróbuj zdefiniować Moduły w zależności od funkcji/funkcjonalności lub widoku , a nie według typu. Zobacz prezentację Misko po szczegóły.

Komponenty modułu mogą być konwencjonalnie grupowane według typów, takich jak kontrolery, modele, widoki, filtry, dyrektywy itp.

Ale sam moduł pozostaje wielokrotnego użytku, zbywalne i testowalne.

Jest również znacznie łatwiej dla programistów znaleźć niektóre części kodu i wszystkie jego zależności.

Proszę zapoznać się z Organizacja kodu w dużych AngularJS i aplikacji JavaScript aby uzyskać szczegóły.

Przykład struktury folderów :

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

Dobry przykład struktury aplikacji kątowej jest zaimplementowany przez angular-app - https://github.com/angular-app/angular-app/tree/master/client/src

Jest to również rozważane przez nowoczesne Generatory aplikacji - 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/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:36

Uważam, że podejście Igora do tego, jak widać w cytacie, który podałeś, jest tylko wierzchołkiem góry lodowej o wiele większego problemu.

MVC i jego pochodne (MVP, PM, MVVM) są dobre i eleganckie w obrębie jednego agenta, ale architektura Serwer-Klient jest dla wszystkich celów systemem dwóch agentów, a ludzie często mają taką obsesję na punkcie tych wzorców, że zapominają, że problem jest o wiele bardziej złożony. Starając się przestrzegać tych zasad faktycznie kończą z wadliwa Architektura.

Zróbmy to kawałek po kawałku.

Wytyczne

Views

W kontekście kątowym, widok jest DOM. Wytyczne są następujące:

Do:

  • zmienna Present scope (tylko do odczytu).
  • wywołanie kontrolera dla akcji.

Nie:

  • postaw dowolną logikę.

Jak kuszące, krótkie i nieszkodliwe to wygląda:

ng-click="collapsed = !collapsed"

To właściwie oznacza każdego dewelopera, który teraz do zrozumieć, jak działa system muszą sprawdzić zarówno pliki Javascript, jak i pliki HTML.

Kontrolery

Do:

  • Połącz widok z "modelem", umieszczając dane w zakresie.
  • odpowiadaj na akcje użytkownika.
  • zajmij się logiką prezentacji.

Nie:

  • radzić sobie z każdą logiką biznesową.

Powodem ostatniej wytycznej jest to, że kontrolerzy są siostrami wobec poglądów, a nie Bytów; ani nie są wielokrotnego użytku.

Można by argumentować, że dyrektywy są wielokrotnego użytku, ale dyrektywy też są siostrzane dla poglądów (DOM) - nigdy nie miały odpowiadać Bytom.

Jasne, czasami widoki reprezentują byty, ale to raczej specyficzny przypadek.

Innymi słowy, Kontrolery powinny skupić się na prezentacji - jeśli dorzucisz logikę biznesową, nie tylko skończysz z nadmuchanym, mało zarządzalnym kontrolerem, ale także naruszysz separację zasada troski .

[[11]}jako takie, Kontrolery w Angular są naprawdę bardziej Model prezentacji lub MVVM.

A więc, jeśli kontrolerzy nie powinni zajmować się logiką biznesową, to kto powinien?

Co to jest model?

Twój model klienta jest często częściowy i nieświeży]}

Jeśli nie piszesz aplikacji internetowej offline lub aplikacji, która jest strasznie prosta( kilka podmiotów), twój model klienta jest wysoce prawdopodobny be:

  • częściowy
    • albo nie ma wszystkich Bytów (jak w przypadku paginacji)
    • Nie ma w nim wszystkich danych (jak w przypadku paginacji).]}
  • Stale - Jeśli system ma więcej niż jednego użytkownika, w dowolnym momencie nie możesz być pewien, że model, który posiada klient, jest taki sam, jak ten, który przechowuje serwer.

Prawdziwy model musi trwać

W tradycyjnym MCV model jest jedynym rzecz trwała . Ilekroć mówimy o modelach, muszą one być utrzymywane w pewnym momencie. Twój Klient może dowolnie manipulować modelami, ale dopóki podróż do serwera nie zostanie zakończona pomyślnie, zadanie nie zostanie wykonane.

Konsekwencje

Dwa powyższe punkty powinny służyć jako ostrzeżenie - model, który posiada twój Klient, może obejmować tylko częściową, głównie prostą logikę biznesową.

Jako takie, jest być może rozsądne, w kontekście klienta, używać małych liter M - więc to naprawdę mVC, mVP i mVVm. Wielki {[1] } jest dla serwera.

Logika biznesowa

Być może jednym z najważniejszych pojęć dotyczących modeli biznesowych jest to, że można je podzielić na 2 typy (pomijam trzeci view-business jeden, ponieważ to historia na inny dzień):

  • Domain logic - aka Enterprise business rules , logika niezależna od aplikacji. Na przykład podaj model dzięki właściwościom firstName i sirName, getter taki jak getFullName() może być uważany za niezależny od aplikacji.
  • logika aplikacji - aka reguły biznesowe aplikacji , które są specyficzne dla aplikacji. Na przykład Sprawdzanie błędów i obsługa.

Ważne jest, aby podkreślić, że obie z nich w kontekście klienta są , a nie "prawdziwą" logiką biznesową - dotyczą tylko tej części, która jest ważna dla klienta. Logika aplikacji (nie logika domeny) powinien mieć obowiązek ułatwiania komunikacji z serwerem i większości interakcji z użytkownikiem; podczas gdy logika domeny jest w dużej mierze na małą skalę, specyficzna dla jednostki i oparta na prezentacji.

Wciąż pozostaje pytanie-gdzie wrzucić je w aplikacji kątowej?

Architektura 3 vs 4 warstwy

Wszystkie te frameworki MVW używają 3 warstw:

Trzy koła. Wewnętrzny-model, środkowy-kontroler, zewnętrzny-widok

Ale są z tym dwa zasadnicze problemy, jeśli chodzi o klienci:

  • model jest częściowy, nieświeży i nie utrzymuje się.
  • brak miejsca na logikę aplikacji.

Alternatywą dla tej strategii jest 4-warstwowa strategia :

4 okręgi, od wewnętrznych do zewnętrznych-reguły biznesowe dla przedsiębiorstw, reguły biznesowe dla aplikacji, Adaptery interfejsów, frameworki i sterowniki

Prawdziwą sprawą jest warstwa zasad biznesowych aplikacji( przypadki użycia), która często nie sprawdza się w przypadku klientów.

Ta warstwa jest realizowana przez interaktory( Uncle Bob), co jest prawie tym, co Martin Fowler nazywa usługą operation script layer .

Konkretny przykład

Rozważ następującą aplikację internetową:

  • Aplikacja pokazuje stronicowaną listę użytkowników.
  • użytkownik kliknie "Dodaj użytkownika".
  • model otwiera się z formularzem, aby wypełnić dane użytkownika.
  • użytkownik wypełnia formularz i wciska submit.

Kilka rzeczy powinno się teraz wydarzyć:

  • formularz powinien być zatwierdzony przez Klienta.
  • żądanie jest wysyłane do serwera.
  • błąd należy się nim zająć, jeśli taki istnieje.
  • Lista użytkowników może, ale nie musi (ze względu na paginację) wymagać aktualizacji.

Gdzie to wszystko rzucamy?

Jeśli twoja architektura zawiera kontroler, który wywołuje $resource, to wszystko stanie się w kontrolerze. Ale jest lepsza strategia.

Proponowane rozwiązanie

Poniższy diagram pokazuje, jak powyższy problem można rozwiązać dodając kolejną warstwę logiki aplikacji w Angular klienci:

4 pola-DOM wskazuje na kontroler, który wskazuje na logikę aplikacji, która wskazuje na $resource

Dodajemy więc warstwę pomiędzy kontrolerem do $resource, tej warstwy (nazwijmy ją interactor):

  • jest usługą . W przypadku użytkowników można ją nazwać UserInteractor.
  • dostarcza metod odpowiadających przypadkom użycia, enkapsulating application logic .
  • to kontroluje żądania wysyłane do serwera. Zamiast kontrolera wywołującego $resource z parametrami free-form, to warstwa zapewnia, że żądania złożone do serwera zwracają dane, na których logika domeny może działać.
  • dekoruje zwracaną strukturę danych za pomocą prototypu logiki domeny .

I tak, z wymaganiami konkretnego przykładu powyżej:

  • użytkownik kliknie "Dodaj użytkownika".
  • [[28]}kontroler prosi interaktora o pusty model użytkownika, ozdobiony jest metodą logiki biznesowej, jak validate()
  • po złożeniu, kontroler wywołuje model validate() metoda.
  • jeśli błąd się nie powiedzie, kontroler obsłuży błąd.
  • Jeśli się powiedzie, kontroler wywoła interaktor z createUser()
  • interaktor wywołuje $ zasób
  • Po odpowiedzi interaktor przekazuje wszelkie błędy kontrolerowi, który je obsługuje.
  • po pomyślnej odpowiedzi interaktor zapewnia, że w razie potrzeby Lista użytkowników zostanie zaktualizowana.
 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/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-02 21:59:43

Drobny problem w porównaniu z wielkimi radami w odpowiedzi Artema, ale jeśli chodzi o czytelność kodu, uznałem, że najlepiej zdefiniować API całkowicie wewnątrz obiektu return, aby zminimalizować przechodzenie tam iz powrotem w kodzie, aby wyglądać jak zmienne są zdefiniowane:

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, ...) {
    ...
    }
  }
});

Jeśli obiekt return staje się "zbyt zatłoczony", jest to znak, że usługa robi za dużo.

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-03 16:37:23

AngularJS nie implementuje MVC w tradycyjny sposób, raczej implementuje coś bliższego MVVM(Model-View-ViewModel), ViewModel może być również określany jako binder (w przypadku kątowym może to być $scope). Model-- > jak wiemy model w angular może być zwykłym starym obiektem JS lub danymi w naszej aplikacji

Widok-- > widok w angularJS jest HTML, który został parsowany i skompilowany przez angularJS poprzez zastosowanie dyrektyw, instrukcji lub wiązań, główny punkt jest w angular wejście nie jest zwykłym ciągiem HTML( innerHTML), jest raczej DOM tworzonym przez przeglądarkę.

ViewModel -- > ViewModel jest w rzeczywistości spoiwem / mostkiem pomiędzy Twoim widokiem i modelem w przypadku angularJS jest to $scope, aby zainicjować i rozszerzyć $scope używamy kontrolera.

Jeśli chcę podsumować odpowiedź: w aplikacji angularJS $ scope ma odniesienie do danych, Kontroler kontroluje zachowanie, a View obsługuje układ poprzez interakcję z kontrolerem, aby zachować odpowiednio.

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-04 08:02:03

Aby być zdecydowanym na pytanie, Angular używa różnych wzorców projektowych, które już napotkaliśmy w naszym regularnym programowaniu. 1) Kiedy rejestrujemy nasze sterowniki lub dyrektywy, fabrykę, usługi itp. w odniesieniu do naszego modułu. Tutaj ukrywa dane z globalnej przestrzeni. Czyli wzór modułu . 2) gdy kątowy używa swojego brudnego sprawdzania do porównywania zmiennych zakresu, tutaj używa wzoru obserwatora . 3) wszystkie zakresy rodzicielskie w naszych kontrolerach prototypowy wzór. 4) w przypadku zastrzyków usług stosuje wzorzec fabryczny.

Ogólnie używa różnych znanych wzorców projektowych, aby rozwiązać problemy.

 -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/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-08 12:35:52