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
- angularjs ui-router – como construir o estado mestre que é global em todo o aplicativo
- Diferença entre $ state.transitionTo () e $ state.go () no roteador ui Angular
- Angularjs $ state open link em nova aba
- contador de caracteres da área de texto angularjs
- Problema de escopo no AngularJS usando o AngularUI Bootstrap Modal
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:
ngapp.factory('Scopes', function (){ var mem = {}; return { store: function (key, value) { mem[key] = value; }, get: function (key) { return mem[key]; } }; });
ngapp.controller('myCtrl', ['$scope', 'Scopes', function($scope, Scopes) { Scopes.store('myCtrl', $scope); }]);
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:
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); }());