Ich habe einen Service:
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;
}
}
}
};
Aber wenn ich save()
aufrufe, habe ich keinen Zugriff auf $scope
Und erhalte ReferenceError: $scope is not defined
. Der logische Schritt (für mich) ist also, save () mit dem $scope
Zu versorgen, und daher muss ich ihn auch dem service
zur Verfügung stellen/injizieren. Also wenn ich das so mache:
.service('StudentService', [ '$http', '$scope',
function ($http, $scope) {
Ich erhalte folgenden Fehler:
Fehler: [$ injector: unpr] Unbekannter Anbieter: $ scopeProvider <- $ scope <- StudentService
Der Link im Fehler (wow, das ist ordentlich!) Lässt mich wissen, dass er mit dem Injektor zusammenhängt und möglicherweise mit der Reihenfolge der Deklaration der js-Dateien zu tun hat. Ich habe versucht, sie im index.html
Neu zu ordnen, aber ich denke, es ist einfacher, wie ich sie injiziere.
Verwendung von Angular-UI und Angular-UI-Router
Das $scope
, Das Sie sehen, wenn es in Controller injiziert wird, ist kein Service (wie der Rest des injizierbaren Materials), sondern ein Scope-Objekt. Es können viele Bereichsobjekte erstellt werden (die in der Regel prototypisch von einem übergeordneten Bereich erben). Die Wurzel aller Bereiche ist der $rootScope
Und Sie können einen neuen untergeordneten Bereich mit der $new()
-Methode eines beliebigen Bereichs (einschließlich des $rootScope
) Erstellen.
Der Zweck eines Bereichs besteht darin, die Präsentation und die Geschäftslogik Ihrer App "zusammenzufügen". Es macht wenig Sinn, einen $scope
In einen Dienst zu übergeben.
Services sind Singleton-Objekte, die (unter anderem) zum Teilen von Daten (z. B. zwischen mehreren Controllern) verwendet werden und im Allgemeinen wiederverwendbare Codeteile kapseln (da sie in jeden Teil Ihrer App eingefügt werden können und ihre "Services" anbieten können, der sie benötigt: Controller, Richtlinien, Filter, andere Dienste usw.).
Ich bin mir sicher, dass verschiedene Ansätze für Sie funktionieren würden. Eins ist das:
Da das StudentService
für den Umgang mit Studentendaten zuständig ist, können Sie das StudentService
veranlassen, eine Reihe von Studenten zu behalten und diese an alle Interessenten "weiterzugeben" ( zB Ihr $scope
). Dies ist umso sinnvoller, wenn es andere Ansichten/Controller/Filter/Dienste gibt, die Zugriff auf diese Informationen benötigen (wenn es im Moment keine gibt, wundern Sie sich nicht, wenn sie bald auftauchen).
Jedes Mal, wenn ein neuer Schüler hinzugefügt wird (unter Verwendung der save()
-Methode des Dienstes), wird das eigene Schülerarray des Dienstes aktualisiert und jedes andere Objekt, das dieses Array teilt, wird ebenfalls automatisch aktualisiert.
Basierend auf dem oben beschriebenen Ansatz könnte Ihr Code folgendermaßen aussehen:
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.
});
};
}
]);
Eine Sache, bei der Sie vorsichtig sein sollten, wenn Sie diesen Ansatz verwenden, ist, das Array des Dienstes niemals neu zuzuweisen, da dann alle anderen Komponenten (z. B. Bereiche) immer noch auf das ursprüngliche Array verweisen und Ihre App beschädigt wird.
Z.B. So löschen Sie das Array in StudentService
:
/* DON'T DO THAT */
var clear = function () { students = []; }
/* DO THIS INSTEAD */
var clear = function () { students.splice(0, students.length); }
Siehe auch diese kurze Demo.
KLEINES UPDATE:
Ein paar Worte, um die Verwirrung zu vermeiden, die auftreten kann, wenn über die Verwendung eines Dienstes gesprochen wird, diese aber nicht mit der Funktion service()
erstellt wird.
Zitiere das docs on $provide
:
Ein Angular service ist ein Singleton-Objekt, das von einer service factory erstellt wurde. Diese service factories sind Funktionen, die werden wiederum von einem Dienstanbieter erstellt. Die Dienstanbieter sind Konstruktorfunktionen. Wenn sie instanziiert werden, müssen sie eine Eigenschaft mit dem Namen
$get
enthalten, die das - enthält. service factory Funktion.
[...]
... der Dienst$provide
verfügt über zusätzliche Hilfsmethoden zum Registrieren von Diensten ohne Angabe eines Anbieters:
- provider (provider) - registriert einen Dienstanbieter beim $ injector
- constant (obj) - registriert einen Wert/ein Objekt, auf den/das Anbieter und Dienste zugreifen können.
- value (obj) - registriert einen Wert/ein Objekt, auf den/das nur von Diensten zugegriffen werden kann, nicht von Anbietern.
- factory (fn) - registriert eine Service-Factory-Funktion, fn, die in ein Service-Provider-Objekt eingeschlossen wird, dessen Eigenschaft $ get die angegebene Factory-Funktion enthält.
- service (class) - registriert eine Konstruktorfunktion, eine Klasse, die in ein Service-Provider-Objekt eingeschlossen wird, dessen Eigenschaft $ get mit der angegebenen Konstruktorfunktion ein neues Objekt instanziiert.
Grundsätzlich heißt es, dass jeder Angular Dienst mit $provide.provider()
registriert wird, aber es gibt "Shortcut" -Methoden für einfachere Dienste (zwei davon sind service()
und factory()
).
Es "läuft alles auf einen Dienst hinaus", so dass es keinen großen Unterschied macht, welche Methode Sie verwenden (solange die Anforderungen für Ihren Dienst durch diese Methode abgedeckt werden können).
Übrigens, provider
vs service
vs factory
ist eines der verwirrendsten Konzepte für Angular Neulinge, aber zum Glück gibt es viele davon Ressourcen (hier auf SO), um die Dinge einfacher zu machen. (Suche einfach herum.)
(Ich hoffe, das klärt es auf - lass es mich wissen, wenn es nicht klappt.)
Anstatt zu versuchen, den $scope
Innerhalb des Dienstes zu ändern, können Sie einen $watch
In Ihrem Controller implementieren, um eine Eigenschaft Ihres Dienstes auf Änderungen zu überwachen und dann eine Eigenschaft auf dem $scope
. Hier ist ein Beispiel, das Sie in einem Controller ausprobieren könnten:
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;
}
});
}());
}]);
Damit die Eigenschaft students
in Ihrem Service sichtbar ist, muss sie sich auf dem Service-Objekt oder this
wie folgt befinden:
this.students = $http.get(path).then(function (resp) {
return resp.data;
});
Nun (eine lange) ... wenn Sie darauf bestehen $scope
Zugriff auf einen Dienst zu haben, können Sie:
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');
}]);
Dienste sind Singletons, und es ist nicht logisch, dass ein Bereich in den Dienst eingefügt wird (was in der Tat der Fall ist, dass Sie keinen Bereich in den Dienst einfügen können). Sie können scope als Parameter übergeben, aber das ist auch eine schlechte Wahl für das Design, da der Gültigkeitsbereich an mehreren Stellen bearbeitet werden müsste, was das Debuggen erschwert. Der Code für den Umgang mit Bereichsvariablen sollte im Controller gespeichert werden, und Serviceaufrufe werden an den Service gesendet.
Sie können Ihrem Dienst den Bereich vollständig entziehen, in Ihrem Controller jedoch die asynchrone Aktualisierung des Bereichs zulassen.
Das Problem besteht darin, dass Sie nicht wissen, dass http-Aufrufe asynchron ausgeführt werden, was bedeutet, dass Sie nicht sofort einen Wert erhalten, wie Sie es könnten. Zum Beispiel,
var students = $http.get(path).then(function (resp) {
return resp.data;
}); // then() returns a promise object, not resp.data
Es gibt eine einfache Möglichkeit, dies zu umgehen und eine Rückruffunktion bereitzustellen.
.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
})
}
}]);
Die Form:
<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>
Dadurch wurde ein Teil Ihrer Geschäftslogik der Kürze halber entfernt, und ich habe den Code noch nicht getestet, aber so etwas würde funktionieren. Das Hauptkonzept besteht darin, einen Rückruf von der Steuerung an den Dienst zu übergeben, der später in der Zukunft aufgerufen wird. Wenn Sie mit NodeJS vertraut sind, ist dies das gleiche Konzept.
Bin in die gleiche Situation geraten. Am Ende hatte ich Folgendes. Hier injiziere ich also nicht das Scope-Objekt in die Fabrik, sondern setze das $ scope im Controller selbst unter Verwendung des Konzepts von Versprechen von $ http zurückgegeben.
(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);
}());
Der Code für den Umgang mit Bereichsvariablen sollte im Controller gespeichert werden, und Serviceaufrufe werden an den Service gesendet.
Sie können $rootScope
Eingeben, um $rootScope.$broadcast
Und $rootScope.$on
Zu verwenden.
Vermeiden Sie andernfalls die Injektion von $rootScope
. Sehen