Injetando $ scope em uma function de serviço angular ()

Eu tenho um serviço:

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; } } } }; 

Mas quando eu chamo save() , não tenho access ao $scope , e recebo ReferenceError: $scope is not defined . Portanto, o passo lógico (para mim) é fornecer save () com o $scope e, portanto, também devo fornecer / injetá-lo ao service . Então, se eu fizer assim:

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

Estou tendo o erro a seguir:

Erro: [$ injector: unpr] Provedor desconhecido: $ scopeProvider <- $ scope <- StudentService

O link no erro (wow que é legal!) Permite-me saber que é relacionado com o injector, e pode ter a ver com a ordem de declaração dos arquivos js. Eu tentei reordená-los no index.html , mas eu acho que é algo mais simples, como a forma como eu estou injetando-os.

Usando o Angular-UI e o Angular-UI-Router

O $scope que você vê sendo injetado nos controladores não é algum serviço (como o resto do material injetável), mas é um object Escopo. Muitos objects de escopo podem ser criados (geralmente, herdando protótipos de um escopo pai). A raiz de todos os escopos é o $rootScope e você pode criar um novo escopo filho usando o método $new() de qualquer escopo (incluindo o $rootScope ).

O objective de um escopo é “colar” a apresentação e a lógica de negócios do seu aplicativo. Não faz muito sentido passar um $scope para um serviço.

Serviços são objects singleton usados ​​(entre outras coisas) para compartilhar dados (por exemplo, entre vários controladores) e geralmente encapsular pedaços de código reutilizáveis ​​(já que podem ser injetados e oferecer seus “serviços” em qualquer parte do seu aplicativo que precise deles: controladores diretivas, filtros, outros serviços etc).

Tenho certeza de que várias abordagens funcionariam para você. Um é isso:
Como o StudentService é responsável por lidar com os dados dos alunos, você pode fazer com que o StudentService mantenha uma matriz de alunos e “compartilhe” com quem estiver interessado (por exemplo, seu $scope ). Isso faz ainda mais sentido, se houver outras views / controllers / filters / services que precisam ter access a essa informação (se não houver nenhuma agora, não se surpreenda se elas começarem a aparecer em breve).
Toda vez que um novo aluno é adicionado (usando o método save() do serviço), o próprio array de alunos do serviço será atualizado e todos os outros objects compartilhando esse array também serão atualizados automaticamente.

Com base na abordagem descrita acima, seu código poderia ser assim:

 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 }; } ]); 

Uma coisa sobre a qual você deve ter cuidado ao usar essa abordagem é nunca reatribuir a matriz do serviço, porque qualquer outro componente (por exemplo, escopos) ainda estará fazendo referência ao array original e seu aplicativo será interrompido.
Por exemplo, para limpar o array no StudentService :

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

Veja também esta pequena demonstração .


POUCA ATUALIZAÇÃO:

Algumas palavras para evitar a confusão que pode surgir ao falar sobre o uso de um serviço, mas não criá-lo com a function service() .

Citando os documentos em $provide :

Um serviço Angular é um object singleton criado por uma fábrica de serviços . Essas fábricas de serviços são funções que, por sua vez, são criadas por um provedor de serviços . Os provedores de serviços são funções construtoras. Quando instanciados, eles devem conter uma propriedade chamada $get , que mantém a function de fábrica de serviços .
[...]
... o serviço $provide tem methods auxiliares adicionais para registrar serviços sem especificar um provedor:

  • provedor (provedor) - registra um provedor de serviços com o $ injetor
  • constante (obj) - registra um valor / object que pode ser acessado por provedores e serviços.
  • value (obj) - registra um valor / object que só pode ser acessado por serviços, não provedores.
  • factory (fn) - registra uma function de fábrica de serviços, fn, que será envolvida em um object provedor de serviços, cuja propriedade $ get conterá a function de fábrica especificada.
  • service (class) - registra uma function construtora, class que será envolvida em um object de provedor de serviços, cuja propriedade $ get instanciará um novo object usando a function construtora fornecida.

Basicamente, o que ele diz é que todo serviço Angular é registrado usando $provide.provider() , mas existem methods de "atalhos" para serviços mais simples (dois dos quais são service() e factory() ).
Tudo "se resume" a um serviço, portanto, não faz muita diferença qual método você usa (desde que os requisitos para o seu serviço possam ser cobertos por esse método).

BTW, provider vs service vs factory é um dos conceitos mais confusos para os recém-chegados Angular, mas felizmente existem muitos resources (aqui em SO) para tornar as coisas mais fáceis. (Apenas pesquise ao redor.)

(Espero que limpe-se - deixe-me saber se não.)

Em vez de tentar modificar o $scope dentro do serviço, você pode implementar um $watch em seu controlador para observar uma propriedade em seu serviço quanto a alterações e, em seguida, atualizar uma propriedade no $scope . Aqui está um exemplo que você pode tentar em um 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; } }); }()); }]); 

Uma coisa a notar é que dentro do seu serviço, para que a propriedade do students seja visível, ele precisa estar no object Serviço ou assim:

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

Bem (um longo) … se você insiste em ter access ao $scope dentro de um serviço, você pode:

Crie um serviço getter / setter

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

Injetar e armazenar o escopo do controlador nele

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

Agora, pegue o escopo dentro de outro serviço

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

Os serviços são singletons e não é lógico que um escopo seja injetado em serviço (o que, na verdade, não permite injetar o escopo em serviço). Você pode passar o escopo como um parâmetro, mas isso também é uma escolha ruim de design, porque você teria o escopo sendo editado em vários locais, dificultando a debugging. Código para lidar com variables ​​de escopo deve ir no controlador e chamadas de serviço vão para o serviço.

Você poderia tornar seu serviço completamente inconsciente do escopo, mas em seu controlador permite que o escopo seja atualizado de forma assíncrona.

O problema que você está tendo é porque você não sabe que as chamadas http são feitas de forma assíncrona, o que significa que você não obtém um valor imediatamente. Por exemplo,

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

Há uma maneira simples de contornar isso e é fornecer uma function de retorno de chamada.

 .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 }) } }]); 

A forma:

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

Isso removeu um pouco da sua lógica de negócios por brevidade e eu não testei o código, mas algo assim funcionaria. O conceito principal é passar um retorno de chamada do controlador para o serviço que é chamado posteriormente no futuro. Se você estiver familiarizado com o NodeJS, este é o mesmo conceito.

Entrou na mesma situação. Eu terminei com o seguinte. Portanto, aqui não estou injetando o object de escopo na fábrica, mas definindo o escopo $ no próprio controlador usando o conceito de promise retornado pelo serviço $ 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); }());