Dzięki ogromnej ilości cennych źródeł otrzymałem kilka ogólnych zaleceń dotyczących implementacji komponentów w aplikacjach AngularJS:
Kontroler
Kontroler powinien być tylko warstwą pośrednią między modelem a widokiem. Postaraj się, aby był tak cienki, jak to tylko możliwe.
Zdecydowanie zaleca się unikanie logiki biznesowej w kontrolerze. Należy go przenieść do modelu.
Kontroler może komunikować się z innymi kontrolerami za pomocą wywołania metody (możliwe, gdy dzieci chcą komunikować się z rodzicem) lub metod $ emit , $ broadcast i $ on . Emitowane i nadawane wiadomości powinny być ograniczone do minimum.
Kontroler nie powinien przejmować się prezentacją ani manipulacją DOM.
Staraj się unikać zagnieżdżonych kontrolerów . W tym przypadku kontroler nadrzędny jest interpretowany jako model. Zamiast tego wstrzykuj modele jako usługi wspólne.
Zakres w kontrolerze powinien być używany do wiązania modelu z widokiem i
hermetyzowania modelu widoku, jak w przypadku wzorca projektowego modelu prezentacji .
Zakres
Traktuj zakres jako tylko do odczytu w szablonach i tylko do zapisu w kontrolerach . Celem zakresu jest odwoływanie się do modelu, a nie bycie modelem.
Podczas wykonywania dwukierunkowego wiązania (model ng) upewnij się, że nie tworzysz powiązania bezpośrednio z właściwościami zakresu.
Model
Model w AngularJS to singleton zdefiniowany przez usługę .
Model zapewnia doskonały sposób oddzielania danych i wyświetlania.
Modele są głównymi kandydatami do testów jednostkowych, ponieważ zazwyczaj mają dokładnie jedną zależność (pewną formę emitera zdarzeń, w typowym przypadku $ rootScope ) i zawierają wysoce testowalną logikę domeny .
Model należy traktować jako realizację konkretnej jednostki. Opiera się na zasadzie pojedynczej odpowiedzialności. Jednostka jest instancją odpowiedzialną za własny zakres powiązanej logiki, która może reprezentować pojedynczą jednostkę w świecie rzeczywistym i opisywać ją w świecie programowania pod względem danych i stanu .
Model powinien hermetyzować dane aplikacji i zapewniać interfejs API
do uzyskiwania dostępu do tych danych i manipulowania nimi.
Model powinien być przenośny, aby można go było łatwo przenosić do podobnej aplikacji.
Wyodrębnienie logiki jednostki w modelu ułatwiło zlokalizowanie, aktualizację i konserwację.
Model może wykorzystywać metody bardziej ogólnych modeli globalnych, które są wspólne dla całej aplikacji.
Staraj się unikać łączenia innych modeli z modelem przy użyciu wstrzykiwania zależności, jeśli nie jest to tak naprawdę zależne od zmniejszenia sprzężenia komponentów i zwiększenia testowalności i użyteczności jednostek .
Staraj się unikać detektorów zdarzeń w modelach. Utrudnia ich testowanie i generalnie zabija modele w kategoriach zasady pojedynczej odpowiedzialności.
Implementacja modelu
Ponieważ model powinien zawierać pewną logikę w zakresie danych i stanu, powinien architektonicznie ograniczać dostęp do swoich elementów, dzięki czemu możemy zagwarantować luźne powiązania.
Sposobem na to w aplikacji AngularJS jest zdefiniowanie go za pomocą usługi fabrycznej . Pozwoli nam to bardzo łatwo zdefiniować prywatne właściwości i metody, a także zwrócić publicznie dostępne w jednym miejscu, dzięki czemu będzie naprawdę czytelny dla programisty.
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ć fabryki, która zwraca nową możliwą funkcję, ponieważ zaczyna to rozkładać wstrzykiwanie zależności, a biblioteka będzie zachowywać się niezręcznie, szczególnie dla osób trzecich.
Lepszym sposobem osiągnięcia tego samego jest użycie fabryki jako interfejsu API do zwracania kolekcji obiektów z dołączonymi do nich metodami pobierającymi i ustawiającymi.
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 rzecz biorąc, staraj się unikać takich sytuacji i odpowiednio projektuj modele, aby można je było wstrzyknąć do kontrolera i użyć w swoim widoku.
W szczególnym przypadku niektóre metody wymagają globalnej dostępności w aplikacji. Aby było to możliwe, możesz zdefiniować właściwość ' common ' w $ rootScope i powiązać ją z commonModel podczas ładowania aplikacji:
angular.module('app', ['app.common'])
.config(...)
.run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
$rootScope.common = 'commonModel';
}]);
Wszystkie twoje globalne metody będą funkcjonować w ramach „ wspólnej ” własności. To jest jakaś przestrzeń nazw .
Ale nie definiuj żadnych metod bezpośrednio w $ rootScope . Może to prowadzić do nieoczekiwanego zachowania, gdy jest używane z dyrektywą ngModel w zakresie widoku, generalnie zaśmiecając zakres i prowadzi do nadpisania metod zakresu.
Ratunek
Zasób umożliwia interakcję z różnymi źródłami danych .
Powinien być wdrażany zgodnie z zasadą pojedynczej odpowiedzialności .
W szczególności jest to serwer proxy wielokrotnego użytku do punktów końcowych HTTP / JSON.
Zasoby są wprowadzane do modeli i dają możliwość wysyłania / pobierania danych.
Wdrażanie zasobów
Fabryka, która tworzy obiekt zasobów, który umożliwia interakcję ze źródłami danych po stronie serwera zgodnymi ze specyfikacją REST.
Zwrócony obiekt zasobu ma 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 to niepowiązane, luźno powiązane jednostki funkcjonalności, które są niezależne.
Usługi to funkcja, którą Angular przenosi do aplikacji internetowych po stronie klienta po stronie serwera, gdzie usługi są powszechnie używane od dawna.
Usługi w aplikacjach Angular to obiekty zastępcze, które są połączone ze sobą przy użyciu iniekcji zależności.
Angular oferuje różne rodzaje usług. Każdy ma własne przypadki użycia. Aby uzyskać szczegółowe informacje, przeczytaj artykuł Zrozumienie typów usług .
Spróbuj rozważyć główne zasady architektury usług w swojej aplikacji.
Ogólnie zgodnie z Glosariuszem usług internetowych :
Usługa to abstrakcyjny zasób, który reprezentuje możliwość wykonywania zadań, które tworzą spójną funkcjonalność z punktu widzenia podmiotów dostawców i podmiotów żądających. Aby skorzystać z usługi, musi ona zostać zrealizowana przez konkretnego agenta dostawcy.
Struktura po stronie klienta
Ogólnie rzecz biorąc, strona klienta aplikacji jest podzielona na moduły . Każdy moduł powinien być testowalny jako jednostka.
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łów można konwencjonalnie pogrupować według typów, takich jak kontrolery, modele, widoki, filtry, dyrektywy itp.
Ale sam moduł pozostaje wielokrotnego użytku , przenoszalny i testowalny .
Programiści mogą również znacznie łatwiej znaleźć niektóre części kodu i wszystkie jego zależności.
Szczegółowe informacje można znaleźć w sekcji Organizacja kodu w dużych aplikacjach AngularJS i JavaScript .
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 strukturyzacji aplikacji kątowych jest realizowany przez angular-app - https://github.com/angular-app/angular-app/tree/master/client/src
Uwzględniają to również nowoczesne generatory aplikacji - https://github.com/yeoman/generator-angular/issues/109