Dans le cadre de cet article d'Igor Minar, responsable d'AngularJS:
MVC vs MVVM vs MVP . Quel sujet controversé sur lequel de nombreux développeurs peuvent passer des heures et des heures à débattre et à se disputer.
Pendant plusieurs années, AngularJS était plus proche de MVC (ou plutôt d'une de ses variantes côté client), mais au fil du temps et grâce à de nombreuses refactorisations et améliorations d'API, il est maintenant plus proche de MVVM - le $ scope objet pourrait être considéré comme le ViewModel en cours décoré par une fonction que nous appelons un contrôleur .
Être capable de catégoriser un framework et de le placer dans l'un des buckets MV * présente certains avantages. Il peut aider les développeurs à se familiariser avec ses API en facilitant la création d'un modèle mental qui représente l'application en cours de création avec le framework. Cela peut également aider à établir la terminologie utilisée par les développeurs.
Cela dit, je préfère voir les développeurs créer des applications efficaces qui sont bien conçues et qui suivent la séparation des préoccupations, plutôt que de les voir perdre du temps à se disputer sur le non-sens de MV *. Et pour cette raison, je déclare par la présente AngularJS être le framework MVW - Model-View-Whatever . Où tout ce qui signifie « tout ce qui fonctionne pour vous ».
Angular vous offre une grande flexibilité pour bien séparer la logique de présentation de la logique métier et de l'état de présentation. Veuillez l'utiliser pour alimenter votre productivité et la maintenabilité de vos applications plutôt que des discussions animées sur des choses qui à la fin de la journée n'ont pas beaucoup d'importance.
Existe-t-il des recommandations ou des directives pour l'implémentation du modèle de conception AngularJS MVW (Model-View-Whatever) dans les applications côté client?
la source
Réponses:
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 :
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.
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:
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 :
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 :
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
la source
searchModel
ne suit pas les conseils de réutilisation. Il vaudrait mieux importer les constantes via leconstant
service. 3. Une explication de ce que cela signifie ici?:Try to avoid having a factory that returns a new able function
prototype
propriété de l'objet rompt également l'héritage, à la place on peut utiliserCar.prototype.save = ...
object
dans votre expression de liaison bidirectionnelle pour vous assurer d'écrire dans la propriété ou lasetter
fonction exacte . En cas d'utilisation de la propriété directe de votre portée ( sans point ), vous risquez de masquer la propriété cible souhaitée avec la propriété nouvellement créée dans la portée supérieure la plus proche de la chaîne de prototypes lors de l'écriture. Ceci est mieux expliqué dans la présentation de MiskoJe crois que le point de vue d'Igor à ce sujet, comme le montre la citation que vous avez fournie, n'est que la pointe de l'iceberg d'un problème bien plus grave.
MVC et ses dérivés (MVP, PM, MVVM) sont tous bons et dandy au sein d'un seul agent, mais une architecture serveur-client est à toutes fins un système à deux agents, et les gens sont souvent tellement obsédés par ces modèles qu'ils oublient que le problème à résoudre est beaucoup plus complexe. En essayant d'adhérer à ces principes, ils se retrouvent en fait avec une architecture défectueuse.
Faisons cela petit à petit.
Les lignes directrices
Vues
Dans le contexte angulaire, la vue est le DOM. Les lignes directrices sont:
Faire:
Ne pas:
Aussi tentant, court et inoffensif, cela semble:
Cela signifie à peu près tout développeur qui, pour comprendre maintenant le fonctionnement du système, doit inspecter à la fois les fichiers Javascript et les fichiers HTML.
Contrôleurs
Faire:
Ne pas:
La raison de la dernière directive est que les contrôleurs sont les sœurs des vues, pas les entités; ni ils ne sont réutilisables.
Vous pourriez affirmer que les directives sont réutilisables, mais les directives sont également sœurs des vues (DOM) - elles n'ont jamais été conçues pour correspondre à des entités.
Bien sûr, les vues représentent parfois des entités, mais c'est un cas assez spécifique.
En d'autres termes, les contrôleurs doivent se concentrer sur la présentation - si vous intégrez la logique métier, non seulement vous risquez de vous retrouver avec un contrôleur gonflé et peu gérable, mais vous violez également le principe de séparation des préoccupations .
En tant que tels, les contrôleurs en Angular sont vraiment plus de Presentation Model ou MVVM .
Et donc, si les contrôleurs ne doivent pas gérer la logique métier, qui devrait le faire?
Qu'est-ce qu'un modèle?
Votre modèle client est souvent partiel et périmé
À moins que vous n'écriviez une application Web hors ligne ou une application terriblement simple (peu d'entités), votre modèle client est très probablement:
Le vrai modèle doit persister
Dans le MCV traditionnel, le modèle est la seule chose persistante . Chaque fois que nous parlons de modèles, ceux-ci doivent être persistés à un moment donné. Votre client peut manipuler les modèles à volonté, mais tant que l'aller-retour vers le serveur n'est pas terminé avec succès, le travail n'est pas terminé.
Conséquences
Les deux points ci-dessus doivent servir de mise en garde - le modèle de votre client ne peut impliquer qu'une logique commerciale partielle, principalement simple.
En tant que tel, il est peut-être judicieux, dans le contexte du client, d'utiliser des minuscules
M
- c'est donc vraiment mVC , mVP et mVVm . Le grosM
est pour le serveur.Logique métier
L'un des concepts les plus importants concernant les modèles commerciaux est peut-être que vous pouvez les subdiviser en 2 types (j'omets le troisième point de vue commercial car c'est une histoire pour un autre jour):
firstName
etsirName
, un getter commegetFullName()
peut être considéré comme indépendant de l'application.Il est important de souligner que ces deux éléments dans un contexte client ne sont pas de la «vraie» logique commerciale - ils ne traitent que de la partie qui est importante pour le client. La logique d'application (et non la logique de domaine) devrait avoir la responsabilité de faciliter la communication avec le serveur et la plupart des interactions de l'utilisateur; tandis que la logique du domaine est en grande partie à petite échelle, spécifique à l'entité et axée sur la présentation.
La question demeure: où les jetez-vous dans une application angulaire?
Architecture 3 vs 4 couches
Tous ces frameworks MVW utilisent 3 couches:
Mais il y a deux problèmes fondamentaux en ce qui concerne les clients:
Une alternative à cette stratégie est la stratégie à 4 couches :
La vraie affaire ici est la couche de règles métier de l'application (cas d'utilisation), qui va souvent mal sur les clients.
Cette couche est réalisée par des interacteurs (Uncle Bob), ce que Martin Fowler appelle une couche de service de script d'opération .
Exemple concret
Considérez l'application Web suivante:
Quelques choses devraient se passer maintenant:
Où jetons-nous tout cela?
Si votre architecture implique un contrôleur qui appelle
$resource
, tout cela se produira dans le contrôleur. Mais il existe une meilleure stratégie.Une solution proposée
Le diagramme suivant montre comment le problème ci-dessus peut être résolu en ajoutant une autre couche logique d'application dans les clients angulaires:
Nous ajoutons donc une couche entre controller à $ resource, cette couche (appelons-la interactor ):
UserInteractor
.Et donc, avec les exigences de l'exemple concret ci-dessus:
validate()
validate()
méthode modèle .createUser()
la source
Un problème mineur par rapport aux excellents conseils de la réponse d'Artem, mais en termes de lisibilité du code, j'ai trouvé préférable de définir l'API complètement à l'intérieur de l'
return
objet, afin de minimiser les allers-retours dans le code pour voir où les variables sont définies:Si l'
return
objet devient "trop encombré", c'est un signe que le Service en fait trop.la source
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é binder (dans le cas angulaire, cela peut être $ scope). Le modèle -> Comme nous le savons, le modèle en angulaire peut être simplement de vieux objets JS ou les données de notre application
La vue -> la vue dans angularJS est le HTML qui a été analysé et compilé par angularJS en appliquant les directives ou les instructions ou les liaisons, le point principal ici est en angulaire l'entrée n'est pas seulement la chaîne HTML simple (innerHTML), mais plutôt est le DOM créé par le navigateur.
Le 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 augmenter le $ scope que nous utilisons Controller.
Si je veux résumer la réponse: dans l'application angularJS, $ scope fait référence aux données, Controller contrôle le comportement et View gère la mise en page en interagissant avec le contrôleur pour se comporter en conséquence.
la source
Pour être clair sur la question, Angular utilise différents modèles de conception que nous avons déjà rencontrés dans notre programmation régulière. 1) Lorsque nous enregistrons nos contrôleurs ou directives, usine, services, etc. par rapport à notre module. Ici, il cache les données de l'espace global. Quel est le modèle de module . 2) Quand angular utilise sa vérification sale pour comparer les variables de portée, ici, il utilise Observer Pattern . 3) Toutes les portées enfants parents de nos contrôleurs utilisent le modèle prototypique. 4) En cas d'injection des services, il utilise Factory Pattern .
Dans l'ensemble, il utilise différents modèles de conception connus pour résoudre les problèmes.
la source