web-dev-qa-db-fra.com

Où l'état du modèle doit-il être stocké dans Angular.js

Je trouve l'utilisation d'Angular de modèles déroutante. Angular semble adopter l'approche qu'un modèle peut être tout ce que vous aimez - IE Angular n'inclut pas de classe de modèle explicite et vous pouvez utiliser des objets JavaScript Vanilla comme des modèles.

Dans presque tous les Angular exemples que j'ai vus, le modèle est en fait un objet, soit créé à la main, soit renvoyé d'un appel d'API via une ressource. Parce que presque tous les Angular que j'ai examiné est simple, généralement les données de modèle stockées sur $ scope dans un contrôleur et tout état lié au modèle, par exemple la sélection, est également stocké sur $ scope dans le contrôleur. pour les applications/exemples simples, mais cela semble être une simplification excessive lorsque les applications deviennent plus complexes. L'état du modèle stocké dans un contrôleur risque de devenir contextuel et d'être perdu si le contexte change, par exemple; Un contrôleur stockant selectedGallery et selectedPhoto ne peut stocker que global selectedImage, pas un selectedPhoto par galerie. Dans une telle situation, l'utilisation d'un contrôleur par galerie pourrait annuler ce problème, mais cela semblerait inutile et probablement inapproprié et inutile du point de vue de l'interface utilisateur.

La définition angulaire des modèles semble plus proche de ce que je considérerais comme un VO/DTO qui est un objet stupide passé entre le serveur et le client. Mon instinct est d'envelopper un tel objet dans ce que je considérerais comme un modèle - une classe qui maintient un état relatif à la DTO/VO (comme la sélection), propose des mutateurs au besoin pour manipuler la DTO/VO, et informe le reste de la application des modifications aux données sous-jacentes. Évidemment, cette dernière partie est bien prise en charge par les liaisons d'Angular, mais je vois toujours un cas d'utilisation solide pour les deux premières responsabilités.

Cependant, je n'ai pas vraiment vu ce modèle utilisé dans les exemples que j'ai examinés, mais je n'ai pas non plus vu ce que je considérerais comme une alternative évolutive. Angular semble décourager implicitement l'utilisation des services comme modèles en appliquant des singletons (je sais qu'il existe des moyens de contourner ce problème, mais ils ne semblent pas largement utilisés ou approuvés).

Alors, comment dois-je garder l'état des données du modèle?

[Modifier] La deuxième réponse dans cette question est intéressante et proche de ce que j'utilise actuellement.

71
Undistraction

L'état (et les modèles) sont stockés dans $ scope

$ scope est l'objet de stockage de données d'Angular. C'est analogue à une base de données. $ scope lui-même n'est pas le modèle, mais vous pouvez stocker des modèles dans $ scope.

Chaque $ scope a un parent $ scope, jusqu'à $ rootScope formant une structure arborescente qui reflète vaguement votre DOM. Lorsque vous appelez une directive qui nécessite une nouvelle portée $, telle que ng-controller, un nouvel objet $ scope sera créé et ajouté à l'arborescence.

Les objets $ scope sont connectés en utilisant l'héritage prototypique. Cela signifie que si vous ajoutez un modèle à un niveau supérieur dans l'arborescence, il sera disponible pour tous les niveaux inférieurs. Il s'agit d'une fonctionnalité incroyablement puissante qui rend la hiérarchie $ scope presque transparente pour l'auteur du modèle.

Les contrôleurs initialisent $ scope

Le but du contrôleur est d'initialiser $ scope. Le même contrôleur peut initialiser de nombreux objets $ scope dans différentes parties de la page. Le contrôleur est instancié, configure l'objet $ scope, puis se ferme. Vous pouvez utiliser le même contrôleur pour initialiser de nombreuses étendues $ dans différentes parties de la page.

Dans le cas de votre galerie d'images, vous auriez un contrôleur imageGallery que vous appliqueriez ensuite à chaque partie du DOM que vous souhaitez être une galerie en utilisant la directive ng-controller. Cette partie de la page aurait sa propre portée $, que vous utiliseriez pour stocker l'attribut selectedPhoto.

Portées prototypiques

$ scope hérite de son parent en utilisant un héritage prototypique simple jusqu'à $ rootScope, vous pouvez donc stocker vos objets n'importe où sur la hiérarchie qui a du sens. Vous obtenez un arbre d'objets $ scope qui se rapporte approximativement à votre DOM actuel. Si votre DOM change, de nouveaux objets $ scope sont créés pour vous au besoin.

$ scope n'est qu'un simple objet JavaScript. Il n'est pas plus inutile de créer plusieurs objets $ scope que de créer un tableau avec plusieurs objets currentImage. C'est une façon sensée d'organiser votre code.

De cette façon Angular supprime l'ancien problème "où dois-je stocker mes données" que nous trouvons souvent en JavaScript. C'est la source de l'un des très gros gains de productivité que nous obtenons d'Angular .

Vous avez des données globales (par exemple, un ID utilisateur)? stockez-le sur $ rootScope. Vous avez des données locales (par exemple, une image actuelle dans une galerie où il existe plusieurs instances de galerie)? Stockez-le sur l'objet $ scope qui appartient à cette galerie.

$ scope est automatiquement à votre disposition dans la partie correcte du modèle.

Les modèles angulaires sont minces

Venant d'un arrière-plan Rails où nous mettons l'accent sur les modèles lourds et les contrôleurs maigres, j'ai trouvé les modèles `` à peine là '' d'Angular surprenants. En fait, mettre beaucoup de logique métier dans votre modèle entraîne souvent des problèmes la ligne, comme nous le voyons parfois avec le modèle User dans Rails qui, si vous ne faites pas attention, augmentera jusqu'à ce qu'elle devienne impossible à maintenir.

Un modèle angular est simplement un objet JavaScript ou une primitive.

Tout objet peut être un modèle. Les modèles sont généralement définis à l'aide de JSON dans le contrôleur ou AJAXed à partir d'un serveur. Un modèle peut être un objet JSON ou simplement une chaîne, un tableau ou même un nombre.

Bien sûr, rien ne vous empêche d'ajouter des fonctions supplémentaires à votre modèle et de les stocker dans l'objet JSON si vous le souhaitez, mais ce serait le portage dans un paradigme qui ne correspond pas vraiment à Angular.

Les objets angulaires sont généralement des référentiels de données et non des fonctions.

Le modèle à l'avant n'est pas le vrai modèle

Bien sûr, le modèle que vous détenez sur le client n'est pas le vrai modèle. Votre modèle actuel, votre seule source de vérité réside sur le serveur. Nous le synchronisons à l'aide d'une API, mais s'il y a un conflit entre les deux, le modèle de votre base de données est évidemment le vainqueur ultime.

Cela vous donne de l'intimité pour des choses comme les codes de réduction, etc. Le modèle que vous trouvez dans votre frontal est une version synchronisée des propriétés publiques du modèle réel, qui est distant.

La logique métier peut vivre dans les services.

Supposons que vous vouliez écrire une méthode pour faire quelque chose à votre modèle, le synchroniser ou le valider par exemple. Dans d'autres cadres, vous pourriez être tenté d'étendre votre modèle avec une méthode pour ce faire. Dans Angular vous seriez plus susceptible d'écrire un service.

Les services sont des objets singleton. Comme tout autre objet JavaScript, vous pouvez y insérer des fonctions ou des données. Angular est livré avec un tas de services intégrés, tels que $ http. Vous pouvez créer le vôtre et utiliser l'injection de dépendances pour les fournir automatiquement à vos contrôleurs.

Un service peut contenir des méthodes pour parler à une API RESTful par exemple, ou pour valider vos données, ou tout autre travail que vous pourriez avoir besoin de faire.

Les services ne sont pas des modèles

Bien sûr, vous ne devez pas utiliser les services comme modèles. Utilisez-les comme des objets qui peuvent faire des choses. Parfois, ils font des choses avec votre modèle. C'est une façon de penser différente, mais viable.

28
superluminary

Tout d'abord, n'oublions pas que Angular est un framework basé sur le Web et si vous "conservez votre état" uniquement dans un objet, il ne survivra pas à ce que l'utilisateur frappe un rafraîchissement sur son navigateur. Par conséquent, savoir comment conserver l'état des données du modèle dans une application Web signifie déterminer comment vous allez les conserver afin que votre code fonctionne dans un environnement de navigateur.

Angular vous permet de conserver facilement votre état en utilisant:

  1. Un appel à une ressource RESTful $
  2. Une URL représentant une instance de votre modèle

Dans votre exemple simple, le stockage d'actions utilisateur telles que selectedGallery et selectedPhoto peut être représenté à l'aide d'une URL avec quelque chose comme:

// List of galleries
.../gallery

// List of photos in a gallery
.../gallery/23

// A specific photo
.../gallery/23/photo/2

L'URL est essentielle car elle permet à votre utilisateur de parcourir l'historique du navigateur à l'aide des boutons back et forward. Si vous souhaitez partager cet état avec une autre partie de votre application, l'application Web fournit une multitude de méthodes pour vous permettre d'utiliser cookie/localStorage, des cadres/champs cachés ou même de les stocker sur votre serveur.

Une fois que vous avez défini votre stratégie sur la façon de conserver différents états de votre application, il devrait être plus facile de décider si vous souhaitez accéder à ces informations persistantes à l'aide d'un objet singleton tel que fourni par .service ou une instance via .factory.

7
marcoseu

Angular n'a pas d'opinion sur la façon dont vous stockez ce que vous appelez des "objets modèles". Le Angular $scope Existe uniquement en tant que "modèle de vue" aux fins de la gestion de votre interface utilisateur. Je suggère de séparer ces deux concepts dans votre code.

Si vous voulez la finesse de Angular notification de changement de portée ($watch), Vous pouvez utiliser un objet de portée pour stocker vos données de modèle si vous le souhaitez (var myScope = $rootScope.$new() N'utilisez simplement pas le même objet scope auquel votre interface utilisateur est liée.

Je recommande d'écrire des services personnalisés à cette fin. Ainsi, le flux de données se présente comme suit:

AJAX -> Service personnalisé -> Objet d'étendue du modèle -> Contrôleur -> Objet d'étendue de l'interface utilisateur -> DOM

Ou ca:

AJAX -> Services personnalisés -> Anciens objets JavaScript simples -> Contrôleur -> Objet de portée d'interface utilisateur -> DOM

1
djsmith