Inyectar $ scope en una función de servicio angular ()

Tengo un Servicio:

angular.module('cfd') .service('StudentService', [ '$http', function ($http) { // get some data via the $http var path = 'data/people/students.json'; var students = $http.get(path).then(function (resp) { return resp.data; }); //save method create a new student if not already exists //else update the existing object this.save = function (student) { if (student.id == null) { //if this is new student, add it in students array $scope.students.push(student); } else { //for existing student, find this student using id //and update it. for (i in students) { if (students[i].id == student.id) { students[i] = student; } } } }; 

Pero cuando llamo a save() , no tengo acceso al $scope , y me sale ReferenceError: $scope is not defined . Entonces, el paso lógico (para mí) es proporcionar save () con $scope , y así también debo proporcionarlo / inyectarlo al service . Entonces si lo hago así:

  .service('StudentService', [ '$http', '$scope', function ($http, $scope) { 

Obtuve el siguiente error:

Error: [$ injector: unpr] Proveedor desconocido: $ scopeProvider <- $ scope <- StudentService

El enlace en el error (¡wow, eso está bien!) Me deja saber que está relacionado con el inyector, y podría tener que ver con el orden de statement de los archivos js. He intentado reordenarlos en el index.html , pero creo que es algo más simple, como la forma en que los estoy inyectando.

Uso de Angular-UI y Angular-UI-Router

El $scope que ve que se inyecta en los controladores no es un servicio (como el rest de los productos inyectables), sino que es un objeto Scope. Se pueden crear muchos objetos de ámbito (generalmente heredando prototípicamente desde un ámbito principal). La raíz de todos los ámbitos es $rootScope y puede crear un nuevo ámbito hijo utilizando el método $new() de cualquier ámbito (incluido el $rootScope ).

El objective de un Alcance es “unir” la presentación y la lógica comercial de su aplicación. No tiene mucho sentido pasar $scope al servicio.

Los servicios son objetos únicos utilizados (entre otras cosas) para compartir datos (por ejemplo, entre varios controladores) y generalmente encapsulan fragmentos de código reutilizables (ya que pueden inyectarse y ofrecer sus “servicios” en cualquier parte de la aplicación que los necesite): controladores, directivas, filtros, otros servicios, etc.).

Estoy seguro de que varios enfoques funcionarían para usted. Uno es esto:
Dado que StudentService está a cargo de tratar con los datos de los estudiantes, puede hacer que StudentService mantenga una variedad de estudiantes y permita que “lo comparta” con quienes estén interesados ​​(por ejemplo, su $scope ). Esto tiene aún más sentido, si hay otras vistas / controladores / filtros / servicios que necesitan tener acceso a esa información (si no hay ninguna ahora, no se sorprenda si comienzan a aparecer pronto).
Cada vez que se agrega un nuevo alumno (utilizando el método save() del servicio), la matriz de estudiantes del servicio se actualizará y todos los demás objetos que compartan esa matriz también se actualizarán automáticamente.

Según el enfoque descrito anteriormente, su código podría verse así:

 angular.module('cfd', []) .factory('StudentService', ['$http', function ($http) { 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 */ 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; } } } }; /* Populate the students array with students from the server */ $http.get(path).success(function (data) { 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 StudentService.save(student); // The $scope's `students` array will be automatically updated // since it references the StudentService's `students` array // Do some more $scope-specific stuff, // eg show a notification }; } ]); 

Una cosa que debe tener cuidado al usar este enfoque es nunca reasignar la matriz del servicio, porque entonces cualquier otro componente (por ejemplo, ámbitos) seguirá haciendo referencia a la matriz original y su aplicación se romperá.
Por ejemplo, para borrar la matriz en StudentService :

 /* DON'T DO THAT */ var clear = function () { students = []; } /* DO THIS INSTEAD */ var clear = function () { students.splice(0, students.length); } 

Ver, también, esta breve demostración .


PEQUEÑA ACTUALIZACIÓN:

Algunas palabras para evitar la confusión que puede surgir al hablar sobre el uso de un servicio, pero no crearlo con la función service() .

Citando los documentos en $provide :

Un servicio angular es un objeto singleton creado por una fábrica de servicios . Estas fábricas de servicios son funciones que, a su vez, son creadas por un proveedor de servicios . Los proveedores de servicios son funciones de constructor. Cuando se crean instancias, deben contener una propiedad llamada $get , que contiene la función de fábrica de servicio .
[...]
... el servicio $provide tiene métodos auxiliares adicionales para registrar servicios sin especificar un proveedor:

  • proveedor (proveedor) : registra un proveedor de servicios con $ inyector
  • constante (obj) : registra un valor / objeto al que pueden acceder los proveedores y los servicios.
  • value (obj) - registra un valor / objeto al que solo pueden acceder los servicios, no los proveedores.
  • fábrica (fn) : registra una función de fábrica de servicio, fn, que se ajustará a un objeto del proveedor de servicios, cuya propiedad $ get contendrá la función de fábrica especificada.
  • service (clase) : registra una función de constructor, clase que se ajustará a un objeto de proveedor de servicio, cuya propiedad $ get creará una instancia de un nuevo objeto utilizando la función de constructor dada.

Básicamente, lo que dice es que cada servicio angular se registra utilizando $provide.provider() , pero existen métodos de "acceso directo" para servicios más simples (dos de los cuales son service() y factory() ).
Todo se reduce a un servicio, por lo que no importa mucho qué método use (siempre que los requisitos para su servicio puedan ser cubiertos por ese método).

Por cierto, provider vs service vs factory es uno de los conceptos más confusos para recién llegados angulares, pero afortunadamente hay muchos recursos (aquí en SO) para facilitar las cosas. (Solo busca alrededor)

(Espero que eso lo aclare, avíseme si no lo hace).

En lugar de tratar de modificar el $scope dentro del servicio, puede implementar un $watch dentro de su controlador para ver cambios en una propiedad en su servicio y luego actualizar una propiedad en $scope . Aquí hay un ejemplo que puedes probar en un controlador:

 angular.module('cfd') .controller('MyController', ['$scope', 'StudentService', function ($scope, StudentService) { $scope.students = null; (function () { $scope.$watch(function () { return StudentService.students; }, function (newVal, oldVal) { if ( newValue !== oldValue ) { $scope.students = newVal; } }); }()); }]); 

Una cosa a tener en cuenta es que dentro de su servicio, para que la propiedad de los students sea ​​visible, debe estar en el objeto de servicio o algo similar:

 this.students = $http.get(path).then(function (resp) { return resp.data; }); 

Bueno (uno largo) … si insistes en tener acceso $scope dentro de un servicio, puedes:

Crear un servicio getter / setter

 ngapp.factory('Scopes', function (){ var mem = {}; return { store: function (key, value) { mem[key] = value; }, get: function (key) { return mem[key]; } }; }); 

Inyectar y almacenar el scope del controlador en él

 ngapp.controller('myCtrl', ['$scope', 'Scopes', function($scope, Scopes) { Scopes.store('myCtrl', $scope); }]); 

Ahora, consigue el scope dentro de otro servicio

 ngapp.factory('getRoute', ['Scopes', '$http', function(Scopes, $http){ // there you are var $scope = Scopes.get('myCtrl'); }]); 

Los servicios son únicos, y no es lógico que se inyecte un scope en el servicio (lo cual es cierto, no se puede inyectar scope en el servicio). Puede pasar el ámbito como parámetro, pero también es una mala opción de diseño, ya que tendría que editar el scope en varios lugares, lo que dificultaría la depuración. El código para tratar con variables de ámbito debe ir en el controlador y las llamadas de servicio van al servicio.

Puede hacer que su servicio desconozca por completo el scope, pero en su controlador permita que el scope se actualice de forma asincrónica.

El problema que tienes es que no sabes que las llamadas http se realizan de forma asíncrona, lo que significa que no obtienes un valor inmediatamente. Por ejemplo,

 var students = $http.get(path).then(function (resp) { return resp.data; }); // then() returns a promise object, not resp.data 

Hay una forma simple de evitar esto y es proporcionar una función de callback.

 .service('StudentService', [ '$http', function ($http) { // get some data via the $http var path = '/students'; //save method create a new student if not already exists //else update the existing object this.save = function (student, doneCallback) { $http.post( path, { params: { student: student } } ) .then(function (resp) { doneCallback(resp.data); // when the async http call is done, execute the callback }); } .controller('StudentSaveController', ['$scope', 'StudentService', function ($scope, StudentService) { $scope.saveUser = function (user) { StudentService.save(user, function (data) { $scope.message = data; // I'm assuming data is a string error returned from your REST API }) } }]); 

La forma:

 
{{message}}
Name:
E-mail:
Gender: male female

Esto eliminó parte de la lógica de su negocio por brevedad y no he probado realmente el código, pero algo así podría funcionar. El concepto principal es pasar una callback desde el controlador al servicio que se llama más adelante en el futuro. Si está familiarizado con NodeJS, este es el mismo concepto.

Me metí en la misma situación. Terminé con lo siguiente. Así que aquí no estoy inyectando el objeto de scope en la fábrica, sino estableciendo $ scope en el controlador mismo utilizando el concepto de promesa devuelto por el servicio $ http .

 (function () { getDataFactory = function ($http) { return { callWebApi: function (reqData) { var dataTemp = { Page: 1, Take: 10, PropName: 'Id', SortOrder: 'Asc' }; return $http({ method: 'GET', url: '/api/PatientCategoryApi/PatCat', params: dataTemp, // Parameters to pass to external service headers: { 'Content-Type': 'application/Json' } }) } } } patientCategoryController = function ($scope, getDataFactory) { alert('Hare'); var promise = getDataFactory.callWebApi('someDataToPass'); promise.then( function successCallback(response) { alert(JSON.stringify(response.data)); // Set this response data to scope to use it in UI $scope.gridOptions.data = response.data.Collection; }, function errorCallback(response) { alert('Some problem while fetching data!!'); }); } patientCategoryController.$inject = ['$scope', 'getDataFactory']; getDataFactory.$inject = ['$http']; angular.module('demoApp', []); angular.module('demoApp').controller('patientCategoryController', patientCategoryController); angular.module('demoApp').factory('getDataFactory', getDataFactory); }());