$scope injecteren in een hoekservicefunctie()

Ik heb een 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;
          }
        }
      }
    };

Maar als ik save()aanroep, heb ik geen toegang tot de $scopeen krijg ik ReferenceError: $scope is not defined. Dus de logische stap (voor mij) is om save() te voorzien van de $scope, en dus moet ik het ook leveren/injecteren in de service. Dus als ik dat zo doe:

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

Ik krijg de volgende foutmelding:

Fout: [$injector:unpr] Onbekende provider: $scopeProvider <- $scope <-
Studentenservice

De link in de fout (wauw, dat is netjes!) laat me weten dat het injectorgerelateerd is en mogelijk te maken heeft met de volgorde van declaratie van de js-bestanden. Ik heb geprobeerd ze opnieuw te ordenen in de index.html, maar ik denk dat het iets eenvoudiger is, zoals de manier waarop ik ze injecteer.

Angular-UI en Angular-UI-Router gebruiken


Antwoord 1, autoriteit 100%

De $scopedie je ziet worden geïnjecteerd in controllers is niet een of andere service (zoals de rest van de injecteerbare dingen), maar is een Scope-object. Er kunnen veel scope-objecten worden gemaakt (meestal prototypisch overervend van een bovenliggende scope). De root van alle scopes is de $rootScopeen je kunt een nieuwe child-scope maken met behulp van de $new()methode van elke scope (inclusief de $rootScope).

Het doel van een Scope is om de presentatie en de bedrijfslogica van uw app aan elkaar te “lijmen”. Het heeft niet veel zin om een ​​$scopedoor te geven aan een service.

Services zijn singleton-objecten die (onder andere) worden gebruikt om gegevens te delen (bijvoorbeeld tussen verschillende controllers) en in het algemeen herbruikbare stukjes code in te kapselen (omdat ze kunnen worden geïnjecteerd en hun “services” kunnen aanbieden in elk deel van uw app dat ze nodig heeft : controllers, richtlijnen, filters, andere diensten enz.).

Ik weet zeker dat verschillende benaderingen voor jou zouden werken. Een daarvan is deze:
Aangezien de StudentServiceverantwoordelijk is voor het omgaan met studentgegevens, kunt u de StudentServiceeen reeks studenten laten bijhouden en deze laten “delen” met iedereen die mogelijk geïnteresseerd is (bijv. uw $scope). Dit is nog logischer als er andere weergaven/controllers/filters/services zijn die toegang tot die informatie moeten hebben (als die er nu niet zijn, wees dan niet verbaasd als ze binnenkort verschijnen).

Elke keer dat een nieuwe student wordt toegevoegd (met behulp van de save()-methode van de service), wordt de eigen reeks studenten van de service bijgewerkt en wordt elk ander object dat die array deelt ook automatisch bijgewerkt.

Op basis van de hierboven beschreven aanpak zou uw code er als volgt uit kunnen zien:

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.
        });
      };
    }
]);


Een ding waar u voorzichtig mee moet zijn wanneer u deze benadering gebruikt, is dat u de array van de service nooit opnieuw toewijst, omdat dan alle andere componenten (bijv. scopes) nog steeds verwijzen naar de originele array en uw app kapot gaat.
bijv. om de array in StudentServicete wissen:

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

Zie ook deze korte demo.


KLEINE UPDATE:

Een paar woorden om verwarring te voorkomen die kan ontstaan ​​als u spreekt over het gebruik van een service, maar deze niet maakt met de functie service().

Citeer de documenten op $provide:

Een Angular serviceis een singleton-object dat is gemaakt door een servicefabriek. Deze servicefabriekenzijn functies die op hun beurt worden gemaakt door een serviceprovider. De serviceproviderszijn constructorfuncties. Wanneer ze worden geïnstantieerd, moeten ze een eigenschap bevatten met de naam $get, die de functie service factorybevat.
[…]
…de service $provideheeft aanvullende hulpmethoden om services te registreren zonder een provider op te geven:

  • provider(provider)– registreert een serviceprovider bij de $injector
  • constant(obj)– registreert een waarde/object dat toegankelijk is voor providers en services.
  • value(obj)– registreert een waarde/object dat alleen toegankelijk is voor services, niet voor providers.
  • factory(fn)– registreert een service factory-functie, fn, die zal worden verpakt in een serviceprovider-object, waarvan de $get-eigenschap de gegeven fabrieksfunctie zal bevatten.
  • service(class)– registreert een constructorfunctie, klasse die zal worden ingepakt in een serviceproviderobject, waarvan de eigenschap $get een nieuw object zal instantiëren met behulp van de gegeven constructorfunctie.

Kortom, wat er staat, is dat elke Angular-service is geregistreerd met $provide.provider(), maar er zijn “snelkoppelingen” voor eenvoudigere services (waarvan twee service()en factory()).
Het komt allemaal neer op een service, dus het maakt niet veel uit welke methode u gebruikt (zolang de vereisten voor uw service door die methode kunnen worden gedekt).

BTW, providervs servicevs factoryis een van de meest verwarrende concepten voor Angular-nieuwkomers, maar gelukkig zijn er tal van bronnen (hier op SO) om dingen gemakkelijker te maken. (Zoek gewoon rond.)

(Ik hoop dat dit het duidelijk maakt – laat het me weten als dat niet het geval is.)


Antwoord 2, autoriteit 10%

In plaats van te proberen de $scopebinnen de service aan te passen, kunt u een $watchin uw controller implementeren om een ​​eigenschap op uw service te controleren op wijzigingen en deze vervolgens bij te werken een eigenschap op de $scope. Hier is een voorbeeld dat je zou kunnen proberen in een 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;
                }
            });
        }());
    }]);

Een ding om op te merken is dat binnen uw service, om ervoor te zorgen dat de eigenschap studentszichtbaar is, deze zich in het Service-object moet bevinden of thiszoals:

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

Antwoord 3, autoriteit 6%

Nou (een lange) … als u aandringtom $scopetoegang te hebben binnen een service, kunt u:

Maak een getter/setter-service

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

Injecteer het en sla het bereik van de controller erin op

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

Haal het bereik nu in een andere service

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

Antwoord 4, autoriteit 5%

Services zijn singletons, en het is niet logisch dat een scope in service wordt geïnjecteerd (wat inderdaad het geval is, je kunt geen scope in service injecteren). Je kunt scope als parameter doorgeven, maar dat is ook een slechte ontwerpkeuze, omdat je scope op meerdere plaatsen zou moeten bewerken, wat het debuggen moeilijk maakt. Code voor het omgaan met bereikvariabelen moet in de controller gaan en serviceoproepen gaan naar de service.

Other episodes