Le $scope
que vous voyez être injecté dans les contrôleurs n'est pas un service (comme le reste des éléments injectables), mais un objet Scope. De nombreux objets d'étendue peuvent être créés (généralement en héritant de manière prototypique d'une étendue parent). La racine de toutes les étendues est le $rootScope
et vous pouvez créer une nouvelle étendue enfant en utilisant la $new()
méthode de n'importe quelle étendue (y compris la $rootScope
).
Le but d'un Scope est de «coller» la présentation et la logique métier de votre application. Cela n'a pas beaucoup de sens de $scope
transformer un en service.
Les services sont des objets uniques utilisés (entre autres) pour partager des données (par exemple entre plusieurs contrôleurs) et encapsulent généralement des morceaux de code réutilisables (puisqu'ils peuvent être injectés et offrir leurs «services» dans n'importe quelle partie de votre application qui en a besoin: contrôleurs, directives, filtres, autres services, etc.).
Je suis sûr que diverses approches fonctionneraient pour vous. L'une d'entre elles est la suivante: étant
donné que le StudentService
est chargé de traiter les données des étudiants, vous pouvez avoir le StudentService
garder un tableau d'étudiants et le laisser "partager" avec qui pourrait être intéressé (par exemple votre $scope
). Cela a encore plus de sens, s'il y a d'autres vues / contrôleurs / filtres / services qui doivent avoir accès à ces informations (s'il n'y en a pas pour le moment, ne soyez pas surpris si elles commencent à apparaître bientôt).
Chaque fois qu'un nouvel étudiant est ajouté (en utilisant la save()
méthode du service ), le propre tableau d'étudiants du service sera mis à jour et tous les autres objets partageant ce tableau seront également mis à jour automatiquement.
Basé sur l'approche décrite ci-dessus, votre code pourrait ressembler à ceci:
angular.
module('cfd', []).
factory('StudentService', ['$http', '$q', function ($http, $q) {
var path = 'data/people/students.json';
var students = [];
// In the real app, instead of just updating the students array
// (which will be probably already done from the controller)
// this method should send the student data to the server and
// wait for a response.
// This method returns a promise to emulate what would happen
// when actually communicating with the server.
var save = function (student) {
if (student.id === null) {
students.push(student);
} else {
for (var i = 0; i < students.length; i++) {
if (students[i].id === student.id) {
students[i] = student;
break;
}
}
}
return $q.resolve(student);
};
// Populate the students array with students from the server.
$http.get(path).then(function (response) {
response.data.forEach(function (student) {
students.push(student);
});
});
return {
students: students,
save: save
};
}]).
controller('someCtrl', ['$scope', 'StudentService',
function ($scope, StudentService) {
$scope.students = StudentService.students;
$scope.saveStudent = function (student) {
// Do some $scope-specific stuff...
// Do the actual saving using the StudentService.
// Once the operation is completed, the $scope's `students`
// array will be automatically updated, since it references
// the StudentService's `students` array.
StudentService.save(student).then(function () {
// Do some more $scope-specific stuff,
// e.g. show a notification.
}, function (err) {
// Handle the error.
});
};
}
]);
Une chose à laquelle vous devez faire attention lorsque vous utilisez cette approche est de ne jamais réaffecter le tableau du service, car alors tous les autres composants (par exemple les étendues) référenceront toujours le tableau d'origine et votre application se cassera.
Par exemple, pour effacer le tableau dans StudentService
:
/* DON'T DO THAT */
var clear = function () { students = []; }
/* DO THIS INSTEAD */
var clear = function () { students.splice(0, students.length); }
Voir aussi cette courte démo .
PETITE MISE À JOUR:
Quelques mots pour éviter la confusion qui peut survenir en parlant d'utiliser un service, mais pas de le créer avec la service()
fonction
Citant les documents sur$provide
:
Un angulaire service est un objet singleton créé par une fabrique de services . Ces usines de services sont des fonctions qui, à leur tour, sont créées par un fournisseur de services . Les fournisseurs de services sont des fonctions de constructeur. Lorsqu'elles sont instanciées, elles doivent contenir une propriété appelée $get
, qui contient la fonction de fabrique de service .
[...]
... le $provide
service dispose de méthodes d'assistance supplémentaires pour enregistrer les services sans spécifier de fournisseur:
- provider (provider) - enregistre un fournisseur de services avec l'injecteur $
- constant (obj) - enregistre une valeur / un objet auquel les fournisseurs et les services peuvent accéder.
- value (obj) - enregistre une valeur / un objet qui n'est accessible que par les services, pas par les fournisseurs.
- factory (fn) - enregistre une fonction de fabrique de service, fn, qui sera encapsulée dans un objet fournisseur de services, dont la propriété $ get contiendra la fonction de fabrique donnée.
- service (class) - enregistre une fonction constructeur, classe qui sera encapsulée dans un objet fournisseur de services, dont la propriété $ get instanciera un nouvel objet en utilisant la fonction constructeur donnée.
Fondamentalement, ce qu'il dit, c'est que chaque service Angular est enregistré en utilisant $provide.provider()
, mais il existe des méthodes de «raccourci» pour des services plus simples (dont deux sont service()
et factory()
).
Tout se résume à un service, donc peu importe la méthode que vous utilisez (tant que les exigences de votre service peuvent être couvertes par cette méthode).
BTW, provider
vs service
vs factory
est l'un des concepts les plus déroutants pour les nouveaux arrivants angulaires, mais heureusement, il existe de nombreuses ressources (ici sur SO) pour faciliter les choses. (Il suffit de chercher.)
(J'espère que cela clarifie les choses - faites-moi savoir si ce n'est pas le cas.)
service
oufactory
- vous finirez avec un service angulaire . Assurez-vous simplement de comprendre comment chacun fonctionne et si cela répond à vos besoins.$scope.students
il être vide si l'appel ajax n'est pas terminé? Ou$scope.students
va-t-il être partiellement rempli, si ce bloc de code fonctionne en cours?students.push(student);
Au lieu d'essayer de modifier le
$scope
dans le service, vous pouvez implémenter un$watch
dans votre contrôleur pour surveiller une propriété de votre service pour les modifications, puis mettre à jour une propriété sur le$scope
. Voici un exemple que vous pourriez essayer dans un contrôleur:Une chose à noter est qu'au sein de votre service, pour que la
students
propriété soit visible, elle doit être sur l'objet Service outhis
similaire:la source
Eh bien (une longue) ... si vous insistez pour avoir
$scope
accès à l'intérieur d'un service, vous pouvez:Créer un service getter / setter
Injectez-le et stockez-y la portée du contrôleur
Maintenant, obtenez la portée dans un autre service
la source
Les services sont des singletons, et il n'est pas logique qu'une étendue soit injectée en service (ce qui est bien le cas, vous ne pouvez pas injecter d'étendue en service). Vous pouvez passer la portée en tant que paramètre, mais c'est aussi un mauvais choix de conception, car vous auriez une portée en cours de modification à plusieurs endroits, ce qui rendrait le débogage difficile. Le code pour traiter les variables d'étendue doit aller dans le contrôleur et les appels de service vont au service.
la source
Vous pouvez rendre votre service complètement inconscient de l'étendue, mais dans votre contrôleur, autorisez la mise à jour de l'étendue de manière asynchrone.
Le problème que vous rencontrez est que vous ne savez pas que les appels http sont effectués de manière asynchrone, ce qui signifie que vous n'obtenez pas une valeur immédiatement comme vous le pourriez. Par exemple,
Il existe un moyen simple de contourner cela et de fournir une fonction de rappel.
La forme:
Cela a supprimé une partie de votre logique métier par souci de brièveté et je n'ai pas réellement testé le code, mais quelque chose comme ça fonctionnerait. Le concept principal est de transmettre un rappel du contrôleur au service qui sera appelé plus tard dans le futur. Si vous connaissez NodeJS, c'est le même concept.
la source
.then
méthodes de promesse sont-ils un anti-modèle .Je suis dans la même situation. J'ai fini avec ce qui suit. Donc ici je n'injecte pas l'objet scope dans la fabrique, mais je règle le $ scope dans le contrôleur lui-même en utilisant le concept de promesse retourné par le service $ http .
la source
Le code pour traiter les variables d'étendue doit aller dans le contrôleur et les appels de service vont au service.
Vous pouvez injecter
$rootScope
dans le but d'utiliser$rootScope.$broadcast
et$rootScope.$on
.Sinon, évitez de vous injecter
$rootScope
. Voir$rootScope
existe, mais il peut être utilisé pour le mal .la source