J'ai un service, disons:
factory('aService', ['$rootScope', '$resource', function ($rootScope, $resource) {
var service = {
foo: []
};
return service;
}]);
Et je voudrais utiliser foo
pour contrôler une liste qui est rendue en HTML:
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
Afin que le contrôleur détecte quand aService.foo
est mis à jour, j'ai bricolé ce modèle où j'ajoute un service au contrôleur $scope
et utilise ensuite $scope.$watch()
:
function FooCtrl($scope, aService) {
$scope.aService = aService;
$scope.foo = aService.foo;
$scope.$watch('aService.foo', function (newVal, oldVal, scope) {
if(newVal) {
scope.foo = newVal;
}
});
}
Cela semble long, et je l'ai répété dans chaque contrôleur qui utilise les variables du service. Existe-t-il un meilleur moyen d'accomplir l'observation des variables partagées?
aService.foo
dans le balisage html? (comme ceci: plnkr.co/edit/aNrw5Wo4Q0IxR2loipl5?p=preview )Réponses:
Vous pouvez toujours utiliser le bon vieux modèle d'observateur si vous voulez éviter la tyrannie et les frais généraux de
$watch
.Dans le service:
Et dans le contrôleur:
la source
$destory
événement sur la portée et ajoutez une méthode de désenregistrement àaService
$watch
vs modèle d'observateur consiste simplement à choisir d'interroger ou de pousser, et est fondamentalement une question de performance, alors utilisez-le lorsque la performance est importante. J'utilise un modèle d'observation alors que sinon je devrais regarder "en profondeur" des objets complexes. J'attache des services entiers à la portée $ au lieu de regarder des valeurs de service uniques. J'évite la veille angulaire comme le diable, il y en a assez dans les directives et dans la liaison de données angulaire native.Dans un scénario comme celui-ci, où plusieurs objets inconnus peuvent être intéressés par des modifications, utilisez-les à
$rootScope.$broadcast
partir de l'élément modifié.Plutôt que de créer votre propre registre d'écouteurs (qui doivent être nettoyés lors de divers $ destroys), vous devriez pouvoir accéder au
$broadcast
service en question.Vous devez toujours coder les
$on
gestionnaires de chaque écouteur, mais le modèle est découplé de plusieurs appels vers$digest
et évite ainsi le risque d'observateurs de longue durée.De cette façon, les auditeurs peuvent également aller et venir à partir du DOM et / ou de différentes étendues enfants sans que le service ne modifie son comportement.
** mise à jour: exemples **
Les diffusions auraient le plus de sens dans les services "mondiaux" qui pourraient avoir un impact sur d'innombrables autres choses dans votre application. Un bon exemple est un service utilisateur où il y a un certain nombre d'événements qui pourraient avoir lieu tels que la connexion, la déconnexion, la mise à jour, l'inactivité, etc. même en injectant le service, et il n'a pas besoin d'évaluer les expressions ou les résultats de cache pour inspecter les changements. Il se déclenche et oublie (assurez-vous donc qu'il s'agit d'une notification de feu et d'oubli, pas quelque chose qui nécessite une action)
Le service ci-dessus diffuserait un message à chaque portée lorsque la fonction save () était terminée et que les données étaient valides. Alternativement, s'il s'agit d'une ressource $ ou d'une soumission ajax, déplacez l'appel de diffusion dans le rappel pour qu'il se déclenche lorsque le serveur a répondu. Les diffusions conviennent particulièrement bien à ce modèle car chaque auditeur n'attend que l'événement sans avoir à inspecter la portée de chaque digest $. L'auditeur ressemblerait à:
L'un des avantages de cela est l'élimination de plusieurs montres. Si vous combiniez des champs ou dériviez des valeurs comme dans l'exemple ci-dessus, vous devrez surveiller les propriétés firstname et lastname. Regarder la fonction getUser () ne fonctionnerait que si l'objet utilisateur était remplacé lors des mises à jour, il ne se déclencherait pas si l'objet utilisateur avait simplement ses propriétés mises à jour. Dans ce cas, il faudrait faire une surveillance approfondie et c'est plus intensif.
$ broadcast envoie le message de l'étendue à laquelle il est appelé dans toutes les étendues enfants. Donc, l'appeler à partir de $ rootScope se déclenchera sur chaque étendue. Si vous diffusiez $ à partir de la portée de votre contrôleur, par exemple, il se déclencherait uniquement dans les étendues héritant de la portée de votre contrôleur. $ emit va dans la direction opposée et se comporte de manière similaire à un événement DOM en ce sens qu'il bouillonne dans la chaîne de portée.
Gardez à l'esprit qu'il y a des scénarios où $ broadcast a beaucoup de sens, et il y a des scénarios où $ watch est une meilleure option - surtout si dans une portée isolée avec une expression de surveillance très spécifique.
la source
J'utilise une approche similaire à @dtheodot mais en utilisant une promesse angulaire au lieu de passer des rappels
Ensuite, utilisez la
myService.setFoo(foo)
méthode pour mettre à jourfoo
le service. Dans votre contrôleur, vous pouvez l'utiliser comme:Les deux premiers arguments
then
sont les rappels de réussite et d'erreur, le troisième est notifier le rappel.Référence pour $ q.
la source
$scope.$watch
sur une variable de service ne semblait pas fonctionner (la portée que je regardais était un modal hérité de$rootScope
) - cela a fonctionné. Astuce cool, merci pour le partage!Sans montres ni rappels d'observateurs ( http://jsfiddle.net/zymotik/853wvv7s/ ):
JavaScript:
HTML
Ce JavaScript fonctionne lorsque nous renvoyons un objet du service plutôt qu'une valeur. Lorsqu'un objet JavaScript est renvoyé d'un service, Angular ajoute des montres à toutes ses propriétés.
Notez également que j'utilise 'var self = this' car j'ai besoin de garder une référence à l'objet d'origine lorsque le $ timeout s'exécute, sinon 'this' fera référence à l'objet window.
la source
$scope.count = service.count
ne fonctionne pas.$scope.data = service.data
<p>Count: {{ data.count }}</p>
Je suis tombé sur cette question à la recherche de quelque chose de similaire, mais je pense qu'elle mérite une explication approfondie de ce qui se passe, ainsi que des solutions supplémentaires.
Lorsqu'une expression angulaire telle que celle que vous avez utilisée est présente dans le code HTML, Angular configure automatiquement un
$watch
for$scope.foo
et met à jour le code HTML à chaque$scope.foo
modification.Le problème non dit ici est que l'une des deux choses affecte de
aService.foo
telle sorte que les changements ne sont pas détectés. Ces deux possibilités sont:aService.foo
est mis à un nouveau tableau à chaque fois, ce qui rend la référence obsolète.aService.foo
est mis à jour de manière à ce$digest
qu'aucun cycle ne se déclenche sur la mise à jour.Problème 1: Références obsolètes
Compte tenu de la première possibilité, en supposant que a
$digest
est appliqué, s'ilaService.foo
était toujours le même tableau, le paramètre défini automatiquement$watch
détecterait les modifications, comme indiqué dans l'extrait de code ci-dessous.Solution 1-a: assurez-vous que le tableau ou l'objet est le même objet à chaque mise à jour
Afficher l'extrait de code
Comme vous pouvez le voir, la répétition ng supposément attachée à
aService.foo
ne se met pas à jour lors desaService.foo
changements, mais la répétition ng attachée à leaService2.foo
fait . C'est parce que notre référence àaService.foo
est dépassée, mais notre référence àaService2.foo
ne l'est pas. Nous avons créé une référence au tableau initial avec$scope.foo = aService.foo;
, qui a ensuite été supprimée par le service lors de sa prochaine mise à jour, ce qui signifie qu'il$scope.foo
ne faisait plus référence au tableau que nous voulions.Cependant, bien qu'il existe plusieurs façons de s'assurer que la référence initiale est maintenue intacte, il peut parfois être nécessaire de modifier l'objet ou le tableau. Ou que faire si la propriété de service fait référence à une primitive comme un
String
ouNumber
? Dans ces cas, nous ne pouvons pas simplement nous fier à une référence. Alors, que pouvons- nous faire?Plusieurs des réponses données précédemment donnent déjà des solutions à ce problème. Cependant, je suis personnellement en faveur de l'utilisation de la méthode simple suggérée par Jin et les haut - parleurs dans les commentaires:
Solution 1-b: Attachez le service à l'étendue et référence
{service}.{property}
dans le HTML.Signification, faites juste ceci:
HTML:
JS:
Afficher l'extrait de code
De cette façon, le
$watch
sera résoluaService.foo
sur chacun$digest
, ce qui obtiendra la valeur correctement mise à jour.C'est un peu ce que vous essayez de faire avec votre solution de contournement, mais de manière beaucoup moins détournée. Vous avez ajouté un inutile
$watch
dans le contrôleur qui met explicitementfoo
le$scope
chaque fois qu'il change. Vous n'avez pas besoin de cet extra$watch
lorsque vous attachezaService
au lieu deaService.foo
à$scope
, et que vous vous liez explicitement àaService.foo
dans le balisage.Maintenant, tout va bien en supposant qu'un
$digest
cycle est appliqué. Dans mes exemples ci-dessus, j'ai utilisé le$interval
service d' Angular pour mettre à jour les tableaux, qui lance automatiquement une$digest
boucle après chaque mise à jour. Mais que faire si les variables de service (pour une raison quelconque) ne sont pas mises à jour dans le "monde angulaire". En d' autres termes, nous DonT avons un$digest
cycle étant activé automatiquement chaque fois que la propriété du service change?Problème 2: manquant
$digest
De nombreuses solutions ici permettront de résoudre ce problème, mais je suis d'accord avec Code Whisperer :
Par conséquent, je préférerais continuer à utiliser la
aService.foo
référence dans le balisage HTML comme indiqué dans le deuxième exemple ci-dessus, et ne pas avoir à enregistrer un rappel supplémentaire dans le contrôleur.Solution 2: utilisez un setter et un getter avec
$rootScope.$apply()
J'ai été surpris que personne n'ait encore suggéré l'utilisation d'un setter et d'un getter . Cette capacité a été introduite dans ECMAScript5 et existe donc depuis des années. Bien sûr, cela signifie que si, pour une raison quelconque, vous devez prendre en charge de très anciens navigateurs, cette méthode ne fonctionnera pas, mais j'ai l'impression que les getters et setters sont largement sous-utilisés en JavaScript. Dans ce cas particulier, ils pourraient être très utiles:
Afficher l'extrait de code
Ici , j'ai ajouté une variable « privée » dans la fonction de service:
realFoo
. Ce get est mis à jour et récupéré à l'aide des fonctionsget foo()
etset foo()
respectivement sur l'service
objet.Notez l'utilisation de
$rootScope.$apply()
dans la fonction set. Cela garantit qu'Angular sera au courant de toute modification apportée àservice.foo
. Si vous obtenez des erreurs «inprog», consultez cette page de référence utile , ou si vous utilisez Angular> = 1.3, vous pouvez simplement l'utiliser$rootScope.$applyAsync()
.Méfiez-vous également de cela si la
aService.foo
mise à jour est très fréquente, car cela pourrait avoir un impact significatif sur les performances. Si les performances sont un problème, vous pouvez configurer un modèle d'observateur similaire aux autres réponses ici à l'aide du setter.la source
services
ne recherche pas les propriétés qui appartiennent au service lui-même.Autant que je sache, vous n'avez pas à faire quelque chose d'aussi élaboré que cela. Vous avez déjà affecté foo du service à votre portée et puisque foo est un tableau (et à son tour un objet, il est attribué par référence!). Donc, tout ce que vous devez faire est quelque chose comme ceci:
Si certaines variables de ce même Ctrl dépendent du changement de foo, alors oui, vous auriez besoin d'une montre pour observer foo et apporter des modifications à cette variable. Mais tant qu'il s'agit d'une simple consultation de référence, il n'est pas nécessaire. J'espère que cela t'aides.
la source
$watch
travailler avec une primitive. Au lieu de cela, je définissais une méthode sur le service qui renvoie la valeur primitive:somePrimitive() = function() { return somePrimitive }
Et j'attribue une propriété de portée de $ à cette méthode:$scope.somePrimitive = aService.somePrimitive;
. J'ai ensuite utilisé la méthode scope dans le HTML:<span>{{somePrimitive()}}</span>
$scope.foo = aService.foo
ne met pas automatiquement à jour la variable d'étendue.Vous pouvez insérer le service dans $ rootScope et regarder:
Dans votre contrôleur:
Une autre option consiste à utiliser une bibliothèque externe comme: https://github.com/melanke/Watch.JS
Fonctionne avec: IE 9+, FF 4+, SF 5+, WebKit, CH 7+, OP 12+, BESEN, Node.JS, Rhino 1.7+
Vous pouvez observer les changements d'un, de plusieurs ou de tous les attributs d'objet.
Exemple:
la source
Vous pouvez regarder les changements au sein même de l'usine, puis diffuser un changement
Puis dans votre contrôleur:
De cette façon, vous mettez tout le code d'usine associé dans sa description, vous ne pouvez compter que sur la diffusion de l'extérieur
la source
== MISE À JOUR ==
Très simple maintenant dans $ watch.
Pen ici .
HTML:
JavaScript:
la source
En vous basant sur la réponse de dtheodor, vous pouvez utiliser quelque chose de similaire au ci-dessous pour vous assurer de ne pas oublier de désenregistrer le rappel ... Certains peuvent cependant s'opposer à la transmission
$scope
à un service.Array.remove est une méthode d'extension qui ressemble à ceci:
la source
Voici mon approche générique.
Dans votre contrôleur
la source
Pour ceux comme moi qui recherchent une solution simple, cela fait presque exactement ce que vous attendez de l'utilisation de $ watch normal dans les contrôleurs. La seule différence est qu'il évalue la chaîne dans son contexte javascript et non sur une portée spécifique. Vous devrez injecter $ rootScope dans votre service, bien qu'il ne soit utilisé que pour se connecter correctement aux cycles de résumé.
la source
face à un problème très similaire, j'ai regardé une fonction dans la portée et j'ai demandé à la fonction de renvoyer la variable service J'ai créé un violon js . vous pouvez trouver le code ci-dessous.
la source
Je suis venu à cette question mais il s'est avéré que mon problème était que j'utilisais setInterval alors que j'aurais dû utiliser le fournisseur angulaire $ interval. C'est également le cas pour setTimeout (utilisez plutôt $ timeout). Je sais que ce n'est pas la réponse à la question du PO, mais cela pourrait aider certains, comme cela m'a aidé.
la source
setTimeout
, ou toute autre fonction non angulaire, mais n'oubliez pas d'envelopper le code dans le rappel avec$scope.$apply()
.J'ai trouvé une très bonne solution sur l'autre thread avec un problème similaire mais une approche totalement différente. Source: AngularJS: la directive $ watch within ne fonctionne pas lorsque la valeur $ rootScope est modifiée
Fondamentalement, la solution y indique de NE PAS utiliser
$watch
car c'est une solution très lourde. Au lieu de cela, ils proposent d'utiliser$emit
et$on
.Mon problème était de regarder une variable dans mon service et de réagir en directive . Et avec la méthode ci-dessus, c'est très facile!
Mon exemple de module / service:
Donc, fondamentalement, je regarde mon
user
- chaque fois qu'il est réglé sur une nouvelle valeur, j'ai$emit
unuser:change
statut.Maintenant, dans mon cas, dans la directive que j'ai utilisée:
Maintenant, dans la directive, j'écoute sur
$rootScope
et sur le changement donné - je réagis respectivement. Très simple et élégant!la source
// service: (rien de spécial ici)
// ctrl:
la source
Un peu moche, mais j'ai ajouté l'enregistrement des variables de portée à mon service pour une bascule:
Et puis dans le contrôleur:
la source
this
de ce service au lieu deself
?return this;
sortir de vos constructeurs ;-)Jetez un oeil à ce plongeur :: c'est l'exemple le plus simple auquel je puisse penser
http://jsfiddle.net/HEdJF/
la source
J'ai vu ici de terribles modèles d'observateurs qui provoquent des fuites de mémoire sur de grandes applications.
Je suis peut-être un peu en retard, mais c'est aussi simple que cela.
La fonction watch surveille les changements de référence (types primitifs) si vous voulez regarder quelque chose comme push de tableau, utilisez simplement:
someArray.push(someObj); someArray = someArray.splice(0);
Cela mettra à jour la référence et mettra à jour la montre de n'importe où. Y compris une méthode getter de services. Tout ce qui est une primitive sera mis à jour automatiquement.
la source
Je suis en retard dans la partie, mais j'ai trouvé une meilleure façon de le faire que la réponse affichée ci-dessus. Au lieu d'attribuer une variable pour contenir la valeur de la variable de service, j'ai créé une fonction attachée à la portée, qui renvoie la variable de service.
manette
Je pense que cela fera ce que vous voulez. Mon contrôleur continue de vérifier la valeur de mon service avec cette implémentation. Honnêtement, c'est beaucoup plus simple que la réponse choisie.
la source
J'ai écrit deux services utilitaires simples qui m'aident à suivre les modifications des propriétés des services.
Si vous voulez sauter la longue explication, vous pouvez aller directement à jsfiddle
Ce service est utilisé dans le contrôleur de la manière suivante: Supposons que vous ayez un service "testService" avec les propriétés 'prop1', 'prop2', 'prop3'. Vous souhaitez surveiller et affecter à l'étendue «prop1» et «prop2». Avec le service de montre, cela ressemblera à ça:
Je le déclencherais à la fin de mon code asynchrone pour déclencher la boucle $ digest. Comme ça:
Donc, tout cela ressemblera à ça (vous pouvez l'exécuter ou ouvrir le violon ):
la source