Lors de l'écriture d'une directive dans AngularJS, comment puis-je décider si je n'ai pas besoin d'une nouvelle étendue, d'une nouvelle étendue enfant ou d'une nouvelle étendue isolée?

265

Je suis à la recherche de lignes directrices que l'on peut utiliser pour aider à déterminer quel type de portée à utiliser lors de l'écriture d'une nouvelle directive. Idéalement, j'aimerais quelque chose de similaire à un organigramme qui me guidera à travers un tas de questions et sort la bonne réponse - pas de nouvelle portée, de nouvelle portée enfant ou de nouvelle portée isolée - mais cela demande probablement trop. Voici mon ensemble dérisoire actuel de directives:

Je suis conscient que l'utilisation d'une directive avec une portée isolée sur un élément force toutes les autres directives sur ce même élément à utiliser la même (une) portée isolée, donc cela ne limite-t-il pas sérieusement quand une portée isolée peut être utilisée?

J'espère que certains membres de l'équipe Angular-UI (ou d'autres qui ont écrit de nombreuses directives) pourront partager leurs expériences.

Veuillez ne pas ajouter de réponse qui dit simplement "utiliser une portée isolée pour les composants réutilisables".

Mark Rajcok
la source
par "portée enfant", vous voulez dire créer une portée dans la fonction de liaison par "portée. $ new ()"? Parce que, comme je le sais, la directive peut avoir une portée isolée ou ne pas l'avoir (il en sera de même pour la portée où elle a été utilisée)
Valentyn Shybanov
3
@ValentynShybanov Setting scope: truecréera une portée enfant en utilisant $scope.new()automatiquement.
Josh David Miller
2
@Valentyn, ce que Josh a dit: donc, les trois possibilités sont scope: false(la valeur par défaut, pas de nouvelle portée), scope: true(nouvelle portée qui hérite du prototype) et scope: { ... }(nouvelle portée isolée).
Mark Rajcok
1
Oui, merci. J'ai raté cette différence entre "vrai" et "{}". Bon à savoir.
Valentyn Shybanov
Il y a un 4ème cas que les gens ont généralement tendance à ignorer .. c'est le "contrôleur de directive" .. Je pense que la question devrait être élargie pour les inclure aussi ... +1 à la question ..
ganaraj

Réponses:

291

Quelle bonne question! Je l' aime entendre ce que les autres ont à dire, mais voici les directives que j'utilise.

La prémisse à haute altitude: la portée est utilisée comme la «colle» que nous utilisons pour communiquer entre le contrôleur parent, la directive et le modèle de directive.

Portée parent:, scope: false donc pas de nouvelle portée du tout

Je n'utilise pas cela très souvent, mais comme l'a dit @MarkRajcok, si la directive n'accède à aucune variable de portée (et évidemment n'en définit aucune!), Cela ne pose aucun problème en ce qui me concerne. Cela est également utile pour les directives enfants qui ne sont utilisées que dans le contexte de la directive parent (bien qu'il y ait toujours des exceptions à cela) et qui n'ont pas de modèle. Fondamentalement, tout ce qui a un modèle n'appartient pas à partager une étendue, car vous exposez intrinsèquement cette étendue pour l'accès et la manipulation (mais je suis sûr qu'il y a des exceptions à cette règle).

À titre d'exemple, j'ai récemment créé une directive qui dessine un graphique vectoriel (statique) à l'aide d'une bibliothèque SVG que je suis en train d'écrire. Il s'agit de $observedeux attributs ( widthet height) et les utilise dans ses calculs, mais il ne définit ni ne lit aucune variable de portée et n'a aucun modèle. Il s'agit d'un bon cas d'utilisation pour ne pas créer une autre étendue; nous n'en avons pas besoin, alors pourquoi s'embêter?

Mais dans une autre directive SVG, cependant, j'avais besoin d'un ensemble de données à utiliser et je devais en outre stocker un tout petit peu d'état. Dans ce cas, l'utilisation de la portée parent serait irresponsable (encore une fois, d'une manière générale). Donc au lieu...

Portée enfant: scope: true

Les directives avec une portée enfant sont contextuelles et sont destinées à interagir avec la portée actuelle.

De toute évidence, un avantage clé de cela sur une portée isolée est que l'utilisateur est libre d'utiliser l'interpolation sur tous les attributs qu'il souhaite; Par exemple, l'utilisation class="item-type-{{item.type}}"d'une directive avec une portée isolée ne fonctionnera pas par défaut, mais fonctionne très bien avec une directive enfant car tout ce qui est interpolé peut toujours être trouvé par défaut dans la portée parent. De plus, la directive elle-même peut évaluer en toute sécurité les attributs et les expressions dans le contexte de sa propre portée sans se soucier de la pollution ou des dommages au parent.

Par exemple, une info-bulle est quelque chose qui vient d'être ajoutée; une portée isolée ne fonctionnerait pas (par défaut, voir ci-dessous) car il est prévu que nous utiliserons ici d'autres directives ou attributs interpolés. L'info-bulle n'est qu'une amélioration. Mais l'info-bulle doit également définir certaines choses sur la portée à utiliser avec une sous-directive et / ou un modèle et évidemment gérer son propre état, il serait donc assez mauvais d'utiliser la portée parent. Nous le polluons ou l'endommagons, et ni l'un ni l'autre n'est bueno.

Je me retrouve à utiliser des portées enfant plus souvent que des portées isolées ou parent.

Isoler la portée: scope: {}

C'est pour les composants réutilisables. :-)

Mais sérieusement, je considère les "composants réutilisables" comme des "composants autonomes". L'intention est qu'elles doivent être utilisées dans un but spécifique, donc les combiner avec d'autres directives ou ajouter d'autres attributs interpolés au nœud DOM n'a pas de sens en soi.

Pour être plus précis, tout ce qui est nécessaire pour cette fonctionnalité autonome est fourni via des attributs spécifiés évalués dans le contexte de la portée parent; ce sont soit des chaînes unidirectionnelles ('@'), des expressions unidirectionnelles ('&'), soit des liaisons de variables bidirectionnelles ('=').

Sur les composants autonomes, il n'est pas logique de devoir lui appliquer d'autres directives ou attributs car il existe par lui-même. Son style est régi par son propre modèle (si nécessaire) et peut contenir le contenu approprié (si nécessaire). Il est autonome, nous l'avons donc mis dans une portée isolée pour dire: "Ne jouez pas avec ça. Je vous donne une API définie à travers ces quelques attributs."

Une bonne pratique consiste à exclure autant de choses basées sur des modèles du lien directif et des fonctions de contrôleur que possible. Cela fournit un autre point de configuration "de type API": l'utilisateur de la directive peut simplement remplacer le modèle! La fonctionnalité est restée la même et son API interne n'a jamais été touchée, mais nous pouvons jouer avec le style et la mise en œuvre DOM autant que nous le devons. ui / bootstrap est un excellent exemple de la façon de bien faire cela parce que Peter & Pawel sont géniaux.

Les portées isolées sont également idéales pour une utilisation avec la transclusion. Prenez des onglets; ils ne sont pas seulement la fonctionnalité tout, mais tout ce qui est à l' intérieur de celui - ci peuvent être évalués librement dans le cadre des parents tout en laissant les pattes (et vitres) pour faire ce qu'ils veulent. Les onglets ont clairement leur propre état , qui appartient à la portée (pour interagir avec le modèle), mais cet état n'a rien à voir avec le contexte dans lequel il a été utilisé - il est entièrement interne à ce qui fait d'une directive tab une directive tab. De plus, cela n'a pas beaucoup de sens d'utiliser d'autres directives avec les onglets. Ce sont des onglets - et nous avons déjà cette fonctionnalité!

Entourez-le de plus de fonctionnalités ou transcluez plus de fonctionnalités, mais la directive est ce qu'elle est déjà.

Cela dit, je dois noter qu'il existe des moyens de contourner certaines des limitations (c'est-à-dire les fonctionnalités) d'une portée isolée, comme l'a suggéré @ProLoser dans sa réponse. Par exemple, dans la section portée enfant, j'ai mentionné l'interpolation sur les attributs non directifs se brisant lors de l'utilisation d'une portée isolée (par défaut). Mais l'utilisateur pourrait, par exemple, simplement utiliser class="item-type-{{$parent.item.type}}"et cela fonctionnerait à nouveau. Donc, s'il existe une raison impérieuse d'utiliser une étendue isolée sur une étendue enfant mais que vous vous inquiétez de certaines de ces limitations, sachez que vous pouvez les contourner presque toutes si vous en avez besoin.

Résumé

Les directives sans nouvelle portée sont en lecture seule; ils sont totalement fiables (c'est-à-dire internes à l'application) et ils ne touchent pas à la prise. Les directives avec une portée enfant ajoutent des fonctionnalités, mais ce ne sont pas les seules fonctionnalités. Enfin, les étendues d'isolement sont destinées aux directives qui sont le but entier; ils sont autonomes, il est donc correct (et le plus "correct") de les laisser devenir voyous.

Je voulais faire part de mes premières réflexions, mais en pensant à plus de choses, je mettrai à jour cela. Mais merde - c'est long pour une réponse SO ...


PS: Totalement tangentiel, mais comme nous parlons de portées, je préfère dire "prototypique" alors que d'autres préfèrent "prototypique", qui semble être plus précis mais qui ne déroule pas du tout bien de la langue. :-)

Josh David Miller
la source
Merci Josh, excellente réponse. Je voulais / j'attendais de longues réponses à cela. Deux choses que je n'ai pas suivies: 1) portée enfant: "l'utilisateur est libre d'utiliser l'interpolation sur tous les attributs qu'il veut". 2) isoler la portée: "ou pas tous, dans le cas de"? "" Pouvez-vous nous en dire un peu plus? (N'hésitez pas à modifier votre message au lieu d'écrire des commentaires si cela est plus facile.)
Mark Rajcok
@MarkRajcok Pour (1), je l'ai changé pour le rendre un peu moins nébuleux - faites-moi savoir si j'ai échoué. Pour (2), c'était une combinaison d'une faute de frappe et d'une mauvaise formulation; J'ai réécrit ce paragraphe pour être plus clair. J'ai également ajouté un ou deux exemples supplémentaires, clarifié quelques autres choses et corrigé quelques fautes de frappe.
Josh David Miller
Comme mentionné dans la réponse - bootstrap pour angular est un excellent exemple de combinaison de ces éléments. J'ai trouvé l'exemple d'accordéon particulièrement utile - GitHub - Accordéon
CalM
Vous avez mentionné que vous utilisez le plus les étendues enfants, je pensais que le modèle de directives réutilisable était le plus courant et j'ai évité d'écrire des directives qui n'étaient destinées qu'à être utilisées une seule fois. Est-ce inutile? Parfois, lorsque mon code HTML devient trop volumineux, j'ai envie de déplacer cette section dans une directive, mais elle ne sera utilisée qu'une seule fois, je la laisse donc simplement en html.
user2483724
2
@ user2483724 Une idée fausse très courante est que les directives "réutilisables" sont celles qui utilisent une portée isolée; non. Si vous regardez les directives pré-packagées, presque aucune d'entre elles n'utilise des étendues isolées - certaines même pas une portée enfant - mais je vous assure qu'elles sont réutilisables! La règle devrait être de savoir comment le champ d'application de la directive est utilisé. S'il s'agit simplement d'économiser de l'espace dans un fichier, je ne suis pas sûr qu'une directive soit la meilleure approche. Il augmente le temps de traitement pour le bien du développeur. Mais si vous devez, allez-y. Ou utilisez un ngInclude. Ou faites-le dans le cadre de votre build. De nombreuses options!
Josh David Miller
52

Ma politique personnelle et mon expérience:

Isolé: un bac à sable privé

Je veux créer un grand nombre de méthodes et de variables de portée qui sont UNIQUEMENT utilisées par ma directive et qui ne sont jamais vues ni directement accessibles par l'utilisateur. Je souhaite mettre en liste blanche les données de portée dont je dispose. Je peux utiliser la transclusion pour permettre à l'utilisateur de revenir à la portée parent (non affecté) . Je ne veux PAS que mes variables et méthodes soient accessibles aux enfants transclus.

Enfant: une sous-section du contenu

Je veux créer des méthodes de portée et variables CAN accessibles par l'utilisateur, mais ne sont pas pertinentes aux étendues environnantes (frères et sœurs et les parents) en dehors du contexte de ma directive. Je voudrais également permettre à TOUTES les données de portée parent de couler de manière transparente.

Aucune: directives simples et en lecture seule

Je n'ai pas vraiment besoin de jouer avec les méthodes ou les variables de portée. Je fais probablement quelque chose qui n'a pas à voir avec les portées (comme l'affichage de plugins jQuery simples, la validation, etc.).

Remarques

  • Vous ne devez pas laisser ngModel ou d'autres choses avoir un impact direct sur votre décision. Vous pouvez contourner les comportements étranges en faisant des choses comme ng-model=$parent.myVal(enfant) ou ngModel: '='(isoler).
  • Isolate + transclude restaurera tous les comportements normaux des directives frères et retourne à la portée parent, alors ne laissez pas cela affecter votre jugement non plus.
  • Ne jouez pas avec la portée sur aucun car c'est comme mettre des données sur la portée pour la moitié inférieure du DOM, mais pas la moitié supérieure, ce qui a un sens.
  • Faites attention aux priorités de la directive (ne disposez pas d'exemples concrets de la façon dont cela peut affecter les choses)
  • Injectez des services ou utilisez des contrôleurs pour communiquer entre les directives avec n'importe quel type de portée. Vous pouvez également faire require: '^ngModel'pour rechercher dans les éléments parents.
ProLoser
la source
1
J'ai peut-être mal compris cette partie: "Isolate + transclude restaurera tout le comportement normal des directives frères". Voir ce plunker . Vous devrez regarder dans la console.
Josh David Miller
1
Merci ProLoser pour vos idées / réponses. Vous êtes l'une des personnes que j'espérais voir ce message si j'ajoutais la balise angularjs-ui.
Mark Rajcok
@JoshDavidMiller quand on parle de directives sur le même élément DOM, les choses deviennent plus compliquées, et vous devriez plutôt jeter un œil à la propriété Priority. La transclusion est plus pertinente pour le contenu des enfants.
ProLoser
1
@ProLoser C'est vrai, mais je ne sais pas ce que vous vouliez dire par cette déclaration. Ils affectent évidemment les enfants, mais comment la portée des directives affecte-t-elle leurs directives de frères et sœurs?
Josh David Miller
18

Après avoir écrit beaucoup de directives, j'ai décidé d'utiliser moins de isolatedportée. Même si c'est cool et que vous encapsulez les données et assurez-vous de ne pas divulguer les données dans la portée parent, cela limite considérablement la quantité de directives que vous pouvez utiliser ensemble. Alors,

Si la directive que vous allez écrire va se comporter entièrement d'elle-même et que vous ne la partagerez pas avec d'autres directives, optez pour une portée isolée . (comme un composant, vous pouvez simplement le brancher, avec peu de personnalisation pour le développeur final) (cela devient très compliqué lorsque vous essayez d'écrire des sous-éléments qui contiennent des directives)

Si la directive que vous allez écrire va juste faire des manipulations dom qui n'ont besoin d'aucun état interne de portée, ou de modifications explicites de portée (surtout des choses très simples); optez pour aucune nouvelle portée . (tels que ngShow, ngMouseHover, ngClick, ngRepeat)

Si la directive que vous allez écrire doit changer certains éléments dans la portée parent, mais doit également gérer un état interne, optez pour une nouvelle portée enfant . (comme ngController)

N'oubliez pas de consulter le code source des directives: https://github.com/angular/angular.js/tree/master/src/ng/directive
Cela aide grandement à savoir comment les penser

Umur Kontacı
la source
Si plusieurs composants doivent communiquer entre eux, ils peuvent avoir une portée et une utilisation isolées require, ce qui permet de découpler vos directives. Alors, comment cela limite-t-il les possibilités? Cela rend encore plus spécifiques les directives (déclarez donc de quoi vous dépendez). Je ne laisserais donc qu'une seule règle: si votre directive a un état ou a besoin de données de portée où elle est utilisée - utilisez une portée isolée. Sinon, n'utilisez pas la portée. Et à propos des "enfants" - j'ai également écrit pas mal de directives et je n'ai jamais eu besoin de cette fonctionnalité. Si "doit changer certains éléments dans la portée parent" - utilisez les liaisons.
Valentyn Shybanov
Et également sur "doit changer certains éléments dans la portée parent" - si vous modifiez quelque chose dans la portée enfant, les changements ne sont pas remplis dans la portée parent (sauf si vous utilisez un $parenthack sale ). Donc, en réalité, les «étendues enfants» pour les directives sont quelque chose qui devrait être utilisé assez à l'arrière - comme ngRepeatcela crée de nouvelles étendues enfants pour chaque élément à répéter (mais il le crée également en utilisant scope.$new();et non scope: true.
Valentyn Shybanov
1
Vous ne pouvez pas demander plusieurs étendues isolées dans le même élément, vous ne pouvez pas accéder aux fonctions dans l'étendue parent, sauf si vous les liez explicitement. (Bonne chance en utilisant ngClicketc.) Exiger crée une sorte de découplage, je suis d'accord, mais vous devez toujours être au courant de la directive parent. À moins que ce ne soit comme un composant , je suis contre l'isolement. Les directives (au moins la plupart d'entre elles) sont censées être hautement réutilisables et l'isolement rompt cela.
Umur Kontacı
Je n'utilise pas non plus de portée enfant dans les directives, mais étant donné qu'une portée enfant hérite de manière prototype de la portée parent, si l'accès à une propriété dans une propriété de la portée parent, les modifications sont renseignées. Les auteurs d'Angular en ont parlé lors du Meetup MTV, il est "bon d'avoir un point quelque part" youtube.com/watch?v=ZhfUv0spHCY
Umur Kontacı
5
Tout d'abord, je pense que vous êtes un peu trop sévère sur les portées isolées. Je pense qu'ils ont une applicabilité plus large que ce que vous leur attribuez et qu'il existe des moyens d'éviter bon nombre des défis que vous avez (correctement) signalés lorsque nous les utilisons. Je suis également en désaccord avec "peu de personnalisation pour le développeur final" - voir ma réponse pour plus de détails. Cela dit, votre réponse n'était ni mauvaise ni mauvaise et elle répondait à la question, je ne sais donc pas pourquoi elle a été rejetée. Alors, +1.
Josh David Miller
9

Je pensais juste ajouter ma compréhension actuelle et comment elle se rapporte à d'autres concepts JS.

Par défaut (par exemple, non déclaré ou étendue: faux)

Ceci est philosophiquement équivalent à l'utilisation de variables globales. Votre directive peut accéder à tout dans le contrôleur parent, mais elle les affecte également et est affectée en même temps.

portée:{}

C'est comme un module, tout ce qu'il veut utiliser doit être passé explicitement. Si chaque directive que vous utilisez est une portée isolée, cela peut être l'équivalent de créer chaque fichier JS que vous écrivez son propre module avec beaucoup de surcharge pour injecter toutes les dépendances.

portée: enfant

C'est le juste milieu entre les variables globales et le passthrough explicite. Il est similaire à la chaîne de prototypes de javascript et vous étend simplement une copie de la portée parent. Si vous créez une portée isolée et transmettez chaque attribut et fonction de la portée parent, cela est fonctionnellement équivalent à cela.


La clé est que N'IMPORTE QUELLE directive peut être écrite N'IMPORTE QUELLE manière. Les différentes déclarations de portée sont là pour vous aider à vous organiser. Vous pouvez tout faire un module, ou vous pouvez simplement utiliser toutes les variables globales et être très prudent. Pour faciliter la maintenance, il est préférable de modulariser votre logique en parties logiquement cohérentes.Il y a un équilibre entre une prairie ouverte et une prison fermée. La raison pour laquelle cela est délicat, je crois, c'est que lorsque les gens apprennent cela, ils pensent qu'ils apprennent comment fonctionnent les directives, mais en réalité ils apprennent l'organisation du code / de la logique.

Une autre chose qui m'a aidé à comprendre comment fonctionnent les directives est d'apprendre à propos de ngInclude. ngInclude vous aide à inclure des partiels html. Lorsque j'ai commencé à utiliser des directives, j'ai découvert que vous pouviez utiliser son option de modèle pour réduire votre code, mais je n'attachais vraiment aucune logique.

Bien sûr, entre les directives d'angular et le travail de l'équipe angular-ui , je n'ai pas encore eu à créer ma propre directive qui fasse quelque chose de substantiel, donc mon point de vue à ce sujet peut être complètement faux.

user2483724
la source
2

Je suis d'accord avec Umur. En théorie, les étendues isolées semblent merveilleuses et "portables", mais en créant mon application pour impliquer des fonctionnalités non triviales, j'ai rencontré le besoin d'incorporer plusieurs directives (certaines imbriquées dans d'autres ou en y ajoutant des attributs) afin d'écrire entièrement dans mon propre HTML, qui est le but d'un langage spécifique au domaine.

En fin de compte, c'est trop bizarre d'avoir à passer chaque valeur globale ou partagée dans la chaîne avec plusieurs attributs à chaque appel DOM d'une directive (comme cela est requis avec la portée isolate). Il semble stupide d'écrire à plusieurs reprises tout cela dans le DOM et cela semble inefficace, même si ce sont des objets partagés. Cela complique également inutilement les déclarations de la directive. La solution de contournement de l'utilisation de $ parent pour "atteindre" et récupérer les valeurs de la directive HTML semble être une forme très mauvaise.

Moi aussi, j'ai fini par changer mon application pour avoir principalement des directives de portée enfant avec très peu d'isolats - uniquement celles qui n'ont pas besoin d'accéder à quoi que ce soit du parent autre que ce qu'elles peuvent être transmises via des attributs simples et non répétitifs.

Ayant rêvé du rêve des langages spécifiques au domaine pendant des décennies avant qu'il y ait une telle chose, je suis ravi qu'AngularJS offre cette option et je sais que, comme plus de développeurs travaillent dans ce domaine, nous allons voir des applications très intéressantes qui sont également faciles à écrire, à développer et à déboguer pour leurs architectes.

-- RÉ

Ungallery
la source