web-dev-qa-db-fra.com

AngularJS: comprendre le motif de conception

Dans le contexte de ce post de Igor Minar, responsable d’AngularJS: 

MVC vs MVVM vs MVP. Quel sujet controversé que beaucoup de développeurs peut passer des heures et des heures à débattre et à se disputer.

AngularJS a été pendant plusieurs années plus proche de MVC (ou plutôt de l’une de ses variantes côté client), mais au fil du temps et grâce à de nombreux remaniements et améliorations d’API, il est maintenant plus proche de MVVM - l’objet $ scope pourrait être considéré comme le ViewModel qui est décoré par un fonction que nous appelons un Controller.

Pouvoir classer un cadre et le placer dans l’un des compartiments MV * présente certains avantages . Il peut aider les développeurs à être plus à l'aise avec ses API en le rendant plus facile de créer un modèle mental qui représente l’application que est en cours de construction avec le cadre. Cela peut aussi aider à établir terminologie utilisée par les développeurs.

Cela dit, je préférerais que les développeurs construisent des applications géniales qui sont bien conçu et suivre la séparation des préoccupations, que de les voir gaspiller temps à discuter à propos de MV * absurdité. Et pour cette raison, je déclare par la présente AngularJS à être framework MVW - Model-View-Whatever. Où que ce soit signifie "tout ce qui fonctionne pour vous".

Angular vous donne beaucoup de flexibilité pour une présentation bien séparée logique de la logique métier et de l'état de présentation. S'il vous plaît utilisez-le carburant votre productivité et votre maintenabilité d'application plutôt que chauffées les discussions sur des choses qui, au bout du compte, n’importent pas beaucoup.

Existe-t-il des recommandations ou des instructions pour l’implémentation du modèle de conception AngularJS MVW (Model-View-Whatever) dans les applications côté client?

146
Artem Platonov

Grâce à une quantité considérable de sources précieuses, j'ai quelques recommandations générales pour l'implémentation de composants dans les applications AngularJS:


Manette

  • Le contrôleur doit être juste un intercouche entre modèle et vue. Essayez de le rendre aussi mince que possible.

  • Il est fortement recommandé de éviter la logique métier dans le contrôleur. Il devrait être déplacé pour modeler.

  • Le contrôleur peut communiquer avec d’autres contrôleurs à l’aide de l’appel de méthode (possible lorsque les enfants souhaitent communiquer avec le parent) ou $ emit , $ broadcast et $ on méthodes. Les messages émis et diffusés doivent être réduits au minimum.

  • Le contrôleur devrait ne pas se soucier de la présentation ou de la manipulation du DOM.

  • Essayez de éviter les contrôleurs imbriqués. Dans ce cas, le contrôleur parent est interprété comme un modèle. Injectez les modèles en tant que services partagés à la place.

  • Scope dans le contrôleur doit être utilisé pour binding modèle avec vue et
    encapsulant Voir le modèle comme pour Modèle de présentation le motif de conception.


Portée

Traiter 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 et non 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 l'étendue.


Modèle

Le modèle dans AngularJS est un singleton défini par service.

Le modèle fournit un excellent moyen de séparer les données et l'affichage.

Les modèles sont les premiers candidats pour les tests unitaires, car ils ont généralement exactement une dépendance (une forme d'émetteur d'événement, généralement $ rootScope ) et contiennent des éléments hautement testables domaine logique.

  • Le modèle doit être considéré comme une implémentation d'une unité particulière. Il repose sur le principe de responsabilité unique. Unit est une instance qui est responsable de son propre domaine de logique associée qui peut représenter une entité unique dans le monde réel et le décrire dans le monde de la programmation en termes de données et état.

  • Model doit encapsuler les données de votre application et fournir un API pour accéder à ces données et les manipuler.

  • Le modèle doit être portable pour pouvoir être facilement transporté vers une application similaire.

  • En isolant la logique des unités dans votre modèle, vous avez facilité la localisation, la mise à jour et la maintenance.

  • Model 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 à l’aide de l’injection de dépendance s’il n’est pas réellement dépendant de diminuer le couplage des composants et d’augmenter l’unité testability et tilisabilité.

  • Essayez d'éviter d'utiliser des écouteurs d'événement dans les modèles. Cela les rend plus difficiles à tester et détruit généralement les modèles en termes de principe de responsabilité unique.

Mise en œuvre du modèle

Comme le modèle devrait englober une certaine logique en termes de données et d'état, il devrait limiter, sur le plan architectural, l'accès à ses membres afin de garantir un couplage lâche.

La manière de le faire dans l'application AngularJS consiste à le définir en utilisant factory type de service. Cela nous permettra de définir très facilement les propriétés et méthodes privées et de renvoyer celles accessibles au public en un seul endroit, ce qui le rendra réellement lisible par les développeurs.

n 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 usine qui renvoie une nouvelle fonction capable car celle-ci commence à rompre l'injection de dépendance et la bibliothèque se comportera de manière maladroite, en particulier pour les tiers.

Un meilleur moyen d'accomplir la même chose est d'utiliser la fabrique en tant qu'API pour renvoyer une collection d'objets avec les méthodes getter et setter qui leur sont associé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 correctement vos modèles afin qu'ils puissent être injectés dans le contrôleur et utilisés à votre vue.

Dans certains cas, 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 lors du 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é ‘ common '. C'est une sorte de namespace.

Mais ne définissez aucune méthode directement dans votre $ rootScope . Cela peut entraîner comportement inattend lorsqu'il est utilisé avec la directive ngModel dans votre portée de vue, ce qui jettera généralement le contenu de votre portée et conduira à des problèmes de substitution des méthodes de portée.


Ressource

La ressource vous permet d'interagir avec différentes sources de données.

Devrait être implémenté en utilisant single-responsable-principe.

Dans le 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.

Mise en œuvre des ressources

Une fabrique qui crée un objet ressource qui vous permet d'interagir avec des sources de données RESTful côté serveur.

L'objet ressource renvoyé a des méthodes d'action qui fournissent des comportements de haut niveau sans qu'il soit nécessaire d'interagir avec le service $ http de bas niveau.


Prestations de service

Le ​​modèle et la ressource sont des services.

Les services ne sont pas associés, faiblement couplés unités de fonctionnalité autonomes.

Les services sont une fonctionnalité que Angular apporte aux applications Web côté client depuis le serveur, où les services sont couramment utilisés depuis longtemps.

Les services dans les applications Angular sont des objets substituables qui sont câblés ensemble à l'aide de l'injection de dépendance.

Angular est livré avec différents types de services. Chacun avec ses propres cas d'utilisation. Veuillez lire Comprendre les types de service pour plus de détails.

Essayez de prendre en compte principes de base de l'architecture de service dans votre application.

En général selon Glossaire des services Web :

Un service est une ressource abstraite qui représente une capacité à effectuer 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 fournisseur concret.


Structure côté client

En général, le côté client de l'application est divisé en modules. Chaque module devrait être testable comme une unité.

Essayez de définir des modules en fonction de fonctionnalité/fonctionnalité ou vue, pas par type. Voir présentation de Misko pour plus de détails.

Les composants de 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 des parties de code et toutes ses dépendances.

Veuillez vous référer à Organisation du code dans Grandes applications AngularJS et JavaScript pour plus de détails.

n 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

Le bon exemple de angular structuration d'application est implémenté par angular-app - https://github.com/angular-app/angular-app/tree/master/client/src

Ceci est également considéré par les générateurs d’applications modernes - https://github.com/yeoman/generator-angular/issues/109

223
Artem Platonov

Un problème mineur comparant aux grands conseils de la réponse d'Artem, mais en termes de lisibilité du code, j'ai préféré définir l'API complètement à l'intérieur de l'objet return, afin de minimiser les va-et-vient dans le code afin de rechercher les variables suivantes: 

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

Si l'objet return a l'air "trop ​​encombré", c'est que le service en fait trop.

5
Dmitri Zaitsev

AngularJS n'implémente pas MVC de manière traditionnelle, mais plutôt quelque chose de plus proche de MVVM (Model-View-ViewModel), ViewModel peut également être appelé relieur (dans le cas angulaire, il peut s'agir de $ scope) . Comme nous le savons, le modèle angulaire peut être simplement de vieux objets JS ou les données de notre application

La vue -> la vue dans angularJS est le code HTML qui a été analysé et compilé par angularJS en appliquant les directives, instructions ou liaisons. DOM est créé par le navigateur.

ViewModel -> ViewModel est en fait le liant/pont entre votre vue et votre modèle dans le cas angularJS, c’est $ scope, pour initialiser et étendre la portée $ utilisée par Controller.

Si je veux résumer la réponse: Dans l'application angularJS, $ scope fait référence aux données, le contrôleur contrôle le comportement et View gère la mise en page en interagissant avec le contrôleur pour qu'il se comporte en conséquence.

0
Ashutosh