Iniezione di $ scope in una funzione di servizio angular ()

Ho un servizio:

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

Ma quando chiamo save() , non ho accesso a $scope e get ReferenceError: $scope is not defined . Quindi il passo logico (per me) è fornire save () con $scope , e quindi devo anche fornire / iniettarlo al service . Quindi se lo faccio così:

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

Ottengo il seguente errore:

Errore: [$ injector: unpr] Fornitore sconosciuto: $ scopeProvider <- $ scope <- StudentService

Il link nell’errore (wow che è pulito!) Mi fa sapere che è correlato all’iniettore e potrebbe avere a che fare con l’ordine di dichiarazione dei file js. Ho provato a riordinarli nell’index.html, ma penso che sia qualcosa di più semplice, come il modo in cui li sto iniettando.

Utilizzo di Angular-UI e Angular-UI-Router

L’ $scope che si vede iniettare nei controller non è un servizio (come il resto delle cose iniettabili), ma è un object Scope. Molti oggetti scope possono essere creati (di solito ereditando prototipicamente da un ambito genitore). La radice di tutti gli ambiti è $rootScope ed è ansible creare un nuovo ambito $rootScope utilizzando il metodo $new() di qualsiasi ambito (incluso $rootScope ).

Lo scopo di un ambito è quello di “incollare” la presentazione e la logica di business della tua app. Non ha molto senso passare un $scope in un servizio.

I servizi sono oggetti singleton utilizzati (tra le altre cose) per condividere dati (ad esempio tra più controllori) e generalmente incapsulano pezzi di codice riutilizzabili (poiché possono essere iniettati e offrire i loro “servizi” in qualsiasi parte della tua app che ne ha bisogno: controller, direttive, filtri, altri servizi, ecc.).

Sono sicuro che vari approcci funzionerebbero per te. Uno è questo:
Poiché lo StudentService è incaricato di trattare i dati degli studenti, puoi far sì che lo StudentService tenga una schiera di studenti e lasci che “lo condivida” con chiunque possa essere interessato (ad esempio il tuo $scope ). Ciò ha ancora più senso, se ci sono altre visualizzazioni / controller / filtri / servizi che devono avere accesso a tali informazioni (se non ce ne sono ancora adesso, non sorprenderti se iniziano a spuntare presto).
Ogni volta che viene aggiunto un nuovo studente (utilizzando il metodo save() del servizio), la matrice di studenti del servizio verrà aggiornata e ogni altro object che condivide tale array verrà automaticamente aggiornato.

In base all’approccio sopra descritto, il tuo codice potrebbe essere simile a questo:

 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 che dovresti fare attenzione quando usi questo approccio è non riassegnare mai l'array del servizio, perché qualsiasi altro componente (ad esempio gli ambiti) farà ancora riferimento all'array originale e la tua app si interromperà.
Ad esempio per cancellare la matrice in StudentService :

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

Guarda anche questa breve demo .


PICCOLO AGGIORNAMENTO:

Alcune parole per evitare la confusione che potrebbe sorgere mentre si parla di utilizzare un servizio, ma non di crearlo con la funzione service() .

Citando i documenti su $provide :

Un servizio angular è un object singleton creato da una fabbrica di servizi . Queste fabbriche di servizi sono funzioni che, a loro volta, vengono create da un fornitore di servizi . I fornitori di servizi sono funzioni di costruzione. Quando sono istanziati, devono contenere una proprietà chiamata $get , che contiene la funzione factory di servizio .
[...]
... il servizio $provide ha metodi di supporto aggiuntivi per registrare i servizi senza specificare un fornitore:

  • provider (provider) - registra un fornitore di servizi con $ injector
  • constant (obj) - registra un valore / object a cui possono accedere provider e servizi.
  • value (obj) - registra un valore / object a cui possono accedere solo i servizi, non i provider.
  • factory (fn) - registra una funzione factory di servizio, fn, che sarà racchiusa in un object service provider, la cui proprietà $ get conterrà la funzione factory specificata.
  • service (class) - registra una funzione di costruzione, class che verrà racchiusa in un object service provider, la cui proprietà $ get istanzia un nuovo object utilizzando la funzione di costruzione fornita.

Fondamentalmente, quello che dice è che ogni servizio Angular è registrato usando $provide.provider() , ma ci sono metodi "shortcut" per servizi più semplici (due dei quali sono service() e factory() ).
Tutto si riduce a un servizio, quindi non fa molta differenza quale metodo usi (purché i requisiti per il tuo servizio possano essere coperti da quel metodo).

BTW, provider vs service vs factory è uno dei concetti più confusi per i nuovi arrivati ​​Angular, ma fortunatamente ci sono molte risorse (qui su SO) per semplificare le cose. (Cerca solo in giro.)

(Spero che lo chiarisca - fammi sapere se non è così).

Invece di provare a modificare l’ $scope all’interno del servizio, è ansible implementare un $watch all’interno del controller per vedere una proprietà sul proprio servizio per le modifiche e quindi aggiornare una proprietà su $scope . Ecco un esempio che potresti provare in un controller:

 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 da notare è che all’interno del tuo servizio, affinché la proprietà degli students sia visibile, deve essere sull’object Servizio o così:

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

Bene (uno lungo) … se insisti ad avere accesso a $scope all’interno di un servizio, puoi:

Creare un servizio getter / setter

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

Iniettarlo e memorizzare lo scope del controllore in esso

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

Ora, ottenere l’ambito all’interno di un altro servizio

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

I servizi sono singleton e non è logico che un servizio venga iniettato nel servizio (che è il caso, in effetti, non è ansible iniettare scope nel servizio). È ansible passare l’ambito come parametro, ma si tratta anche di una ctriggers scelta di progettazione, perché si avrebbe la possibilità di modificare l’ambito in più punti, rendendo difficile il debug. Il codice per gestire le variabili dell’oscilloscopio deve andare nel controller e le chiamate di servizio vanno al servizio.

Potresti rendere il tuo servizio completamente inconsapevole dell’ambito, ma nel tuo controller consentire l’aggiornamento dell’oscilloscopio in modo asincrono.

Il problema che stai avendo è perché non sei consapevole che le chiamate http sono fatte in modo asincrono, il che significa che non ottieni immediatamente un valore come potresti. Per esempio,

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

C’è un modo semplice per aggirare questo ed è per fornire una funzione di 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 }) } }]); 

Il modulo:

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

Ciò ha rimosso alcune delle tue logiche di business per brevità e non ho ancora testato il codice, ma qualcosa del genere funzionerebbe. Il concetto principale è passare una richiamata dal controller al servizio che verrà chiamata più tardi in futuro. Se hai familiarità con NodeJS questo è lo stesso concetto.

Sono entrato nella stessa situazione. Ho finito con il seguente. Quindi qui non sto iniettando l’object scope in fabbrica, ma impostando $ scope nel controller stesso usando il concetto di promise restituito dal servizio $ 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); }());