webentwicklung-frage-antwort-db.com.de

Injizieren von $ scope in eine angular Servicefunktion ()

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

105
chris Frisina

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.)

182
gkalpak

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;
});
17
Keith Morris

Nun (eine lange) ... wenn Sie darauf bestehen $scope Zugriff auf einen Dienst zu haben, können Sie:

Erstellen Sie einen Get-/Setter-Service

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

Injizieren Sie es und speichern Sie den Controller-Bereich darin

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

Holen Sie sich jetzt den Gültigkeitsbereich in einen anderen Dienst

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

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.

8
Ermin Dedovic

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.

3
2upmedia

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);    
}());
0
VivekDev

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

0
georgeawg