Внедрение $scope в сервисную функцию angular()
У меня есть Сервис:
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;
}
}
}
};
Но когда я звоню save()
, у меня нет доступа к $scope
, а get ReferenceError: $scope is not defined
. Итак, логический шаг (для меня) заключается в предоставлении save() с помощью $scope
, и поэтому я должен также предоставить/вставить его в service
. Поэтому, если я так делаю:
.service('StudentService', [ '$http', '$scope',
function ($http, $scope) {
Я получаю следующую ошибку:
Ошибка: [$ injector: unpr] Неизвестный поставщик: $scopeProvider < - $scope < - StudentService
Ссылка в ошибке (wow, которая является опрятной!) позволяет мне знать, что это связано с инжектором и может иметь отношение к порядку объявления js файлов. Я попытался переупорядочить их в index.html
, но я думаю, что это нечто более простое, например, как я их ввожу.
Использование Angular -UI и Angular -UI-Router
Ответы
Ответ 1
$scope
, который вы видите впрыснутым в контроллеры, не является некоторой службой (например, остальной частью инъектируемого материала), но является объектом Scope. Многие объекты области могут быть созданы (обычно прототипно наследуется от родительской области). Корень всех областей - это $rootScope
, и вы можете создать новую область с помощью метода $new()
любой области (включая $rootScope
).
Целью области является "склеить" презентацию и бизнес-логику вашего приложения. Не имеет смысла передавать $scope
в службу.
Службы - это одноэлементные объекты, используемые (среди прочего) для обмена данными (например, среди нескольких контроллеров) и обычно инкапсулируют многократно используемые фрагменты кода (поскольку их можно вводить и предлагать свои "сервисы" в любой части вашего приложения, которое их нуждается: контроллеры, директивы, фильтры, другие сервисы и т.д.).
Я уверен, что различные подходы будут работать для вас. Один из них:
Поскольку StudentService
отвечает за данные ученика, вы можете иметь StudentService
сохранить массив учеников и позволить ему "делиться" с кем-то, кто может быть заинтересован (например, ваш $scope
). Это имеет еще больший смысл, если есть другие виды/контроллеры/фильтры/службы, которые должны иметь доступ к этой информации (если их нет сейчас, не удивляйтесь, если они начнут появляться в ближайшее время).
Каждый раз, когда новый ученик добавляется (используя метод service save()
), собственный массив учащихся будет обновляться, и каждый другой объект, совместно использующий этот массив, будет автоматически обновляться.
На основе описанного выше подхода ваш код может выглядеть так:
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 `students` array will be automatically updated
// since it references the StudentService `students` array
// Do some more $scope-specific stuff,
// e.g. show a notification
};
}
]);
<суб > Одна вещь, которую вы должны быть осторожны при использовании этого подхода, - никогда не переустанавливать массив службы, потому что тогда любые другие компоненты (например, области действия) будут по-прежнему ссылаться на исходный массив, и ваше приложение сломается.
Например. для очистки массива в StudentService
:
/* DON'T DO THAT */
var clear = function () { students = []; }
/* DO THIS INSTEAD */
var clear = function () { students.splice(0, students.length); }
суб >
См. также эту короткую демонстрацию.
НЕСКОЛЬКО ОБНОВЛЕНИЕ:
Несколько слов, чтобы избежать путаницы, которая может возникнуть при разговоре об использовании службы, но не создавая ее с помощью функции service()
.
Цитирование документов на $provide
:
Служба Angular - это одноэлементный объект, созданный службой factory. Эти сервисные заводы - это функции, которые, в свою очередь, создаются поставщиком услуг. поставщики услуг - это функции конструктора. При создании экземпляра они должны содержать свойство с именем $get
, которое содержит функцию службы factory.
[...]
... служба $provide
имеет дополнительные вспомогательные методы для регистрации служб без указания поставщика:
- поставщик (поставщик) - регистрирует поставщика услуг с помощью $injector
- константа (obj) - регистрирует значение/объект, к которому могут обращаться поставщики и службы.
- value (obj) - регистрирует значение/объект, к которому могут обращаться только службы, а не провайдеры.
- factory (fn) - регистрирует функцию factory службы fn, которая будет обернута в объект поставщика услуг, свойство $get будет содержать данную функцию factory.
- service (класс) - регистрирует функцию-конструктор, класс, который будет обернут в объект поставщика услуг, свойство $get будет создавать экземпляр нового объекта с использованием данной функции конструктора.
В основном, то, что он говорит, заключается в том, что каждый сервис Angular зарегистрирован с использованием $provide.provider()
, но для более простых сервисов существуют "короткие" методы (два из которых - service()
и factory()
).
Все это "сводится" к сервису, поэтому не имеет большого значения, какой метод вы используете (пока требования к вашему сервису могут быть покрыты этим методом).
BTW, provider
vs service
vs factory
- одна из самых запутанных концепций для новых пользователей Angular, но, к счастью, есть много ресурсов (здесь, на SO), чтобы упростить задачу. (Просто найдите вокруг.)
(Я надеюсь, что это очистит - дайте мне знать, если это не так.)
Ответ 2
Вместо того, чтобы пытаться изменить $scope
внутри службы, вы можете реализовать $watch
внутри вашего контроллера, чтобы посмотреть свойство в своей службе для изменений, а затем обновить свойство на $scope
. Вот пример, который вы можете попробовать в контроллере:
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;
}
});
}());
}]);
Следует отметить, что в вашем сервисе, чтобы свойство students
было видимым, оно должно быть в объекте Service или this
так:
this.students = $http.get(path).then(function (resp) {
return resp.data;
});
Ответ 3
Хорошо (длинный)... если вы настаиваете иметь $scope
доступ внутри службы, вы можете:
Создать услугу getter/setter
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');
}]);
Ответ 4
Службы - это синглтоны, и не логично вводить область в службу (что действительно так, вы не можете вводить область в службу). Вы можете передать область как параметр, но это также плохой выбор дизайна, потому что у вас будет область, редактируемая в нескольких местах, что затрудняет ее отладку. Код для работы с переменными области видимости должен идти в контроллере, а вызовы службы переходят в службу.
Ответ 5
Вы можете полностью отказаться от своей службы, но в вашем контроллере разрешить асинхронное обновление области.
Проблема, с которой вы сталкиваетесь, заключается в том, что вы не знаете, что HTTP-вызовы выполняются асинхронно, а это значит, что вы не получаете значение сразу, как можете. Например,
var students = $http.get(path).then(function (resp) {
return resp.data;
}); // then() returns a promise object, not resp.data
Там есть простой способ обойти это, и он будет обеспечивать функцию обратного вызова.
.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
})
}
}]);
Форма:
<div class="form-message">{{message}}</div>
<div ng-controller="StudentSaveController">
<form novalidate class="simple-form">
Name: <input type="text" ng-model="user.name" /><br />
E-mail: <input type="email" ng-model="user.email" /><br />
Gender: <input type="radio" ng-model="user.gender" value="male" />male
<input type="radio" ng-model="user.gender" value="female" />female<br />
<input type="button" ng-click="reset()" value="Reset" />
<input type="submit" ng-click="saveUser(user)" value="Save" />
</form>
</div>
Это сократило некоторые из вашей бизнес-логики для краткости, и я на самом деле не протестировал код, но что-то вроде этого будет работать. Основной концепцией является передача обратного вызова от контроллера к службе, которая будет вызвана позже в будущем. Если вы знакомы с NodeJS, это та же концепция.
Ответ 6
Попал в такое же затруднительное положение. Я закончил со следующим. Поэтому здесь я не вводя объект области в factory, но устанавливая $scope в самом контроллере, используя концепцию обещание, возвращенную $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);
}());