Grâce à une énorme quantité de sources précieuses, j'ai quelques recommandations générales pour implémenter des composants dans les applications AngularJS:
Manette
Le contrôleur doit être juste un intercalaire entre le modèle et la vue. Essayez de le rendre aussi mince que possible.
Il est fortement recommandé d' éviter la logique métier dans le contrôleur. Il doit être déplacé vers le modèle.
Le contrôleur peut communiquer avec d'autres contrôleurs en utilisant l'invocation de méthode (possible lorsque les enfants veulent communiquer avec le parent) ou les méthodes $ emit , $ broadcast et $ on . Les messages émis et diffusés doivent être réduits au minimum.
Le contrôleur ne doit pas se soucier de la présentation ou de la manipulation du DOM.
Essayez d' éviter les contrôleurs imbriqués . Dans ce cas, le contrôleur parent est interprété comme modèle. Injectez plutôt les modèles en tant que services partagés.
La portée du contrôleur doit être utilisée pour relier le modèle avec la vue et
encapsuler le modèle de vue comme pour le modèle de conception du modèle de présentation .
Portée
Traitez la portée comme en lecture seule dans les modèles et en écriture seule dans les contrôleurs . Le but de la portée est de faire référence au modèle, pas d'être le modèle.
Lorsque vous effectuez une liaison bidirectionnelle (ng-model), assurez-vous de ne pas vous lier directement aux propriétés de la portée.
Modèle
Le modèle dans AngularJS est un singleton défini par service .
Le modèle offre un excellent moyen de séparer les données et les afficher.
Les modèles sont des candidats de premier ordre pour les tests unitaires, car ils ont généralement exactement une dépendance (une certaine forme d'émetteur d'événements, dans le cas courant de $ rootScope ) et contiennent une logique de domaine hautement testable .
Le modèle doit être considéré comme une mise en œuvre d'une unité particulière. Il est basé sur le principe de la responsabilité unique. L'unité est une instance qui est responsable de sa propre portée de logique associée qui peut représenter une seule entité dans le monde réel et la décrire dans le monde de la programmation en termes de données et d'état .
Le modèle doit encapsuler les données de votre application et fournir une API
pour accéder et manipuler ces données.
Le modèle doit être portable pour pouvoir être facilement transporté vers une application similaire.
En isolant la logique d'unité dans votre modèle, vous avez facilité la localisation, la mise à jour et la maintenance.
Le modèle peut utiliser des méthodes de modèles globaux plus généraux communs à l'ensemble de l'application.
Essayez d'éviter la composition d'autres modèles dans votre modèle en utilisant l'injection de dépendances si elle n'est pas vraiment dépendante pour diminuer le couplage des composants et augmenter la testabilité et l' utilisabilité des unités .
Essayez d'éviter d'utiliser des écouteurs d'événements dans les modèles. Cela les rend plus difficiles à tester et tue généralement les modèles en termes de principe de responsabilité unique.
Implémentation du modèle
Comme le modèle doit encapsuler une certaine logique en termes de données et d'état, il doit restreindre architecturalement l'accès à ses membres afin que nous puissions garantir un couplage lâche.
La façon de le faire dans l'application AngularJS est de le définir à l'aide du type de service d' usine . Cela nous permettra de définir très facilement des propriétés et des méthodes privées et de renvoyer celles accessibles au public en un seul endroit, ce qui le rendra vraiment lisible pour le développeur.
Un exemple :
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
};
});
Créer de nouvelles instances
Essayez d'éviter d'avoir une fabrique qui retourne une nouvelle fonction capable car cela commence à décomposer l'injection de dépendances et la bibliothèque se comportera mal, en particulier pour les tiers.
Une meilleure façon d'accomplir la même chose est d'utiliser la fabrique comme API pour renvoyer une collection d'objets avec des méthodes getter et setter qui leur sont attachées.
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
// ...
};
});
Modèle global
En général, essayez d'éviter de telles situations et concevez vos modèles correctement afin qu'ils puissent être injectés dans le contrôleur et utilisés dans votre vue.
Dans un cas particulier, certaines méthodes nécessitent une accessibilité globale au sein de l'application. Pour rendre cela possible, vous pouvez définir la propriété ' common ' dans $ rootScope et la lier à commonModel pendant le démarrage de l'application:
angular.module('app', ['app.common'])
.config(...)
.run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
$rootScope.common = 'commonModel';
}]);
Toutes vos méthodes globales vivront dans la propriété « commune ». C'est une sorte d' espace de noms .
Mais ne définissez aucune méthode directement dans votre $ rootScope . Cela peut entraîner un comportement inattendu lorsqu'il est utilisé avec la directive ngModel dans votre étendue de vue, ce qui jonche généralement votre étendue et conduit à des problèmes de substitution des méthodes de portée.
Ressource
Resource vous permet d'interagir avec différentes sources de données .
Doit être mis en œuvre selon le principe de la responsabilité unique .
Dans un cas particulier, il s'agit d'un proxy réutilisable vers les points de terminaison HTTP / JSON.
Les ressources sont injectées dans les modèles et offrent la possibilité d'envoyer / récupérer des données.
Implémentation des ressources
Une fabrique qui crée un objet de ressource qui vous permet d'interagir avec des sources de données côté serveur RESTful.
L'objet de ressource retourné a des méthodes d'action qui fournissent des comportements de haut niveau sans avoir besoin d'interagir avec le service $ http de bas niveau.
Prestations de service
Le modèle et la ressource sont des services .
Les services sont des unités de fonctionnalités non associées , faiblement couplées et autonomes.
Les services sont une fonctionnalité qu'Angular apporte aux applications Web côté client du côté serveur, où les services sont couramment utilisés depuis longtemps.
Les services dans les applications angulaires sont des objets substituables qui sont câblés ensemble à l'aide de l'injection de dépendances.
Angular est livré avec différents types de services. Chacun avec ses propres cas d'utilisation. Veuillez lire Comprendre les types de services pour plus de détails.
Essayez de prendre en compte les principaux principes de l'architecture de service dans votre application.
En général, selon le glossaire des services Web :
Un service est une ressource abstraite qui représente une capacité à exécuter des tâches qui forment une fonctionnalité cohérente du point de vue des entités fournisseurs et des entités demandeurs. Pour être utilisé, un service doit être réalisé par un agent prestataire concret.
Structure côté client
En général, le côté client de l'application est divisé en modules . Chaque module doit pouvoir être testé en tant qu'unité.
Essayez de définir des modules en fonction de la fonctionnalité / fonctionnalité ou de la vue , et non par type. Voir la présentation de Misko pour plus de détails.
Les composants du module peuvent être classiquement regroupés par types tels que contrôleurs, modèles, vues, filtres, directives, etc.
Mais le module lui-même reste réutilisable , transférable et testable .
Il est également beaucoup plus facile pour les développeurs de trouver certaines parties du code et toutes ses dépendances.
Veuillez vous référer à Organisation du code dans les grandes applications AngularJS et JavaScript pour plus de détails.
Un exemple de structuration de dossiers :
|-- 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
Un bon exemple de structuration d'application angulaire est implémenté par angular-app - https://github.com/angular-app/angular-app/tree/master/client/src
Ceci est également pris en compte par les générateurs d'applications modernes - https://github.com/yeoman/generator-angular/issues/109