$ Kapsamını bir açısal hizmet işlevine () enjekte etmek


108

Hizmetim var:

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

Ama aradığımda , ve al'a save()erişimim yok . Bu yüzden mantıksal adım (benim için), save () 'e sağlamaktır ve bu yüzden onu . Yani bunu böyle yaparsam:$scopeReferenceError: $scope is not defined$scopeservice

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

Şu hatayı alıyorum:

Hata: [$ injector: unpr] Bilinmeyen sağlayıcı: $ kapsamProvider <- $ kapsam <- StudentService

Hatadaki bağlantı (harika!), Bunun enjektörle ilgili olduğunu ve js dosyalarının bildirim sırasına göre yapılabileceğini bilmemi sağlıyor. Onları içinde yeniden sıralamayı denedim index.html, ama sanırım daha basit bir şey, onları enjekte etme şeklim gibi.

Angular-UI ve Angular-UI-Router'ı Kullanma

Yanıtlar:


183

$scopeEğer denetleyicileri enjekte ediliyor gördüklerim (enjektabl şeyler geri kalanı gibi) bazı hizmet değil, ama bir Kapsam nesnesidir. Birçok kapsam nesnesi oluşturulabilir (genellikle prototip olarak bir üst kapsamdan miras alınır). Tüm kapsamların kökü, herhangi bir kapsamın (dahil ) yöntemini $rootScopekullanarak yeni bir alt kapsam oluşturabilirsiniz .$new()$rootScope

Kapsamın amacı, uygulamanızın sunumunu ve iş mantığını "birbirine yapıştırmaktır". $scopeBir hizmete geçmek pek mantıklı değil .

Hizmetler, verileri paylaşmak için (diğer şeylerin yanı sıra) kullanılan tek nesnelerdir (örneğin, birkaç denetleyici arasında) ve genellikle yeniden kullanılabilir kod parçalarını kapsüller (çünkü bunlar uygulamanızın bunlara ihtiyaç duyan herhangi bir bölümünde enjekte edilebilir ve "hizmetlerini" sunarlar: denetleyiciler, yönergeler, filtreler, diğer hizmetler vb.).

Eminim, çeşitli yaklaşımlar sizin için işe yarar. Biri şudur: Öğrenci verileriyle ilgilenmekten sorumlu
olduğu için StudentService, StudentServicebir dizi öğrenciyi tutabilir ve ilgilenebilecek kişilerle (örneğin sizin $scope) "paylaşmasına" izin verebilirsiniz . Bu bilgiye erişmesi gereken başka görünümler / denetleyiciler / filtreler / hizmetler varsa bu daha da mantıklıdır (şu anda yoksa, yakında çıkmaya başlarlarsa şaşırmayın).
Her yeni öğrenci eklendiğinde (hizmetin save()yöntemini kullanarak ), hizmetin kendi öğrenci dizisi güncellenecek ve bu diziyi paylaşan diğer tüm nesneler de otomatik olarak güncellenecektir.

Yukarıda açıklanan yaklaşıma bağlı olarak, kodunuz şöyle görünebilir:

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

Bu yaklaşımı kullanırken dikkat etmeniz gereken bir şey, hizmet dizisini asla yeniden atamamaktır, çünkü diğer bileşenler (örn. Kapsamlar) yine de orijinal diziye referans veriyor olacak ve uygulamanız bozulacaktır.
Örneğin, diziyi temizlemek için StudentService:

/* DON'T DO THAT   */  
var clear = function () { students = []; }

/* DO THIS INSTEAD */  
var clear = function () { students.splice(0, students.length); }

Ayrıca bu kısa demoya bakın .


KÜÇÜK GÜNCELLEME:

Bir servisi kullanmaktan bahsederken ortaya çıkabilecek kafa karışıklığını önlemek için birkaç kelime, ancak onu service()işlevle oluşturmamak .

Dokümanlardan$provide alıntı yapmak :

Bir Angular hizmet , bir hizmet fabrikası tarafından oluşturulan tek bir nesnedir . Bu hizmet fabrikaları , bir hizmet sağlayıcı tarafından oluşturulan işlevlerdir . Servis sağlayıcıları yapıcı fonksiyonları bulunmaktadır. Örneklendiğinde $get, hizmet fabrikası işlevini tutan bir özellik içermelidirler .
[...]
... $providehizmetin, bir sağlayıcı belirtmeden hizmetleri kaydettirmek için ek yardımcı yöntemleri vardır:

  • sağlayıcı (sağlayıcı) - bir hizmet sağlayıcıyı $ enjektör ile kaydeder
  • sabit (obj) - sağlayıcılar ve hizmetler tarafından erişilebilen bir değeri / nesneyi kaydeder.
  • değer (obj) - sağlayıcılar tarafından değil, yalnızca servisler tarafından erişilebilen bir değeri / nesneyi kaydeder.
  • fabrika (fn) - $ get özelliği verilen fabrika işlevini içerecek bir hizmet sağlayıcı nesnesine sarılacak bir hizmet fabrikası işlevi olan fn'yi kaydeder.
  • service (sınıf) - $ get özelliği verilen yapıcı işlevini kullanarak yeni bir nesne başlatacak olan bir hizmet sağlayıcı nesnesine sarılacak bir sınıf olan yapıcı işlevini kaydeder.

Temel olarak söylediği, her Angular servisinin kullanılarak kaydedildiği $provide.provider(), ancak daha basit servisler için "kısayol" metotları olduğu (bunlardan ikisi service()ve factory()).
Her şey bir hizmete "kaynar", bu nedenle hangi yöntemi kullandığınız çok fazla fark yaratmaz (hizmetinizin gereksinimleri bu yöntemle karşılanabildiği sürece).

BTW, providervs servicevs factory, Angular'a yeni gelenler için en kafa karıştırıcı kavramlardan biridir, ancak neyse ki işleri kolaylaştırmak için birçok kaynak (burada SO'da) vardır. (Sadece etrafı araştırın.)

(Umarım bu sorunu çözer, yoksa bana haber verin.)


1
Bir soru. Servis diyorsunuz, ancak kod örneğiniz fabrikayı kullanıyor. Fabrikalar, hizmetler ve sağlayıcılar arasındaki farkı anlamaya yeni başlıyorum, bir hizmeti kullandığım için bir fabrikayla gitmenin en iyi seçenek olduğundan emin olmak istiyorum. Senin örneğinden çok şey öğrendim. Keman ve ÇOK net açıklama için teşekkürler.
chris Frisina

3
@chrisFrisina: Cevabı küçük bir açıklamayla güncelledi. Temelde, bunu kullandığınız takdirde çok fark yaratmaz serviceya factorysen ve u bitireceğiz - Açısal hizmet . Her birinin nasıl çalıştığını ve ihtiyaçlarınıza uygun olup olmadığını anladığınızdan emin olun .
gkalpak

Güzel mesaj! Bana çok yardımcı oluyor!
Oni 1

Sağ ol, kanka! işte benzer bir konu hakkında güzel bir makale stsc3000.github.io/blog/2013/10/26/…
Terafor

@ExpertSystem $scope.studentsAjax çağrısı bitmezse boş olacak mı ? Yoksa $scope.studentsbu kod bloğu devam etmekte ise kısmen doldurulacak mı? students.push(student);
Yc Zhang

18

$scopeHizmetin içinde değişiklik yapmaya çalışmak yerine, $watchdenetleyicinizin içinde bir özelliği, değişiklikler için hizmetinizdeki bir özelliği izlemek ve ardından $scope. İşte bir denetleyicide deneyebileceğiniz bir örnek:

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

Unutulmaması gereken bir nokta, hizmetinizin içinde, studentsmülkün görünür olması için, Hizmet nesnesinde veya thisbenzeri olması gerektiğidir:

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

12

Kuyu (uzun bir) ... eğer ısrar olması $scopebir hizmet içi erişim yapabilirsiniz:

Alıcı / ayarlayıcı hizmeti oluşturun

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

Enjekte edin ve denetleyici kapsamını içinde saklayın

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

Şimdi kapsamı başka bir hizmetin içine alın

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

Kapsamlar nasıl yok ediliyor?
JK.

9

Hizmetler tekildir ve hizmete bir kapsamın enjekte edilmesi mantıklı değildir (bu gerçekten de hizmet kapsamına enjekte edemezsiniz). Kapsamı bir parametre olarak geçirebilirsiniz, ancak bu aynı zamanda kötü bir tasarım seçimidir, çünkü kapsamın birden çok yerde düzenlenmesi, hata ayıklamayı zorlaştırır. Kapsam değişkenleriyle başa çıkma kodu denetleyiciye gitmeli ve hizmet çağrıları hizmete gitmelidir.


Ne dediğini anlıyorum. Ancak benim durumumda birçok denetleyicim var ve kapsamlarını çok benzer bir $ saat setiyle yapılandırmak istiyorum. Bunu nasıl / nerede yapacaksın? Şu anda, kapsamı bir parametre olarak $ saatleri ayarlayan bir hizmete geçiriyorum.
moritz

@moritz ikincil bir yönerge uygulayabilir (kapsamı olan biri: false, dolayısıyla diğer yönergeler tarafından tanımlanan kapsamı kullanır) ve biri watchess'in bağlamalarını ve ihtiyacınız olan her şeyi yapar. Bu şekilde, bu tür saatleri tanımlamak için ihtiyacınız olan herhangi bir yerde diğer yönergeyi kullanabilirsiniz. Çünkü kapsamı bir servise geçirmek gerçekten çok kötü :) (inan bana, oradaydım, bunu yaptım, sonunda kafamı duvara
vurdum

@TIMINeutron, kapsamı dolaşmaktan çok daha iyi geliyor, bir dahaki sefere senaryo geldiğinde bunu deneyeceğim! Teşekkürler!
moritz

Elbette. Hâlâ kendim öğreniyorum ve bu özel sorun, son zamanlarda bu şekilde ele aldığım bir sorun ve benim için bir cazibe merkezi oldu.
tfrascaroli

3

Hizmetinizi kapsamın tamamen farkında olmayabilirsiniz, ancak denetleyicinizde kapsamın eşzamansız olarak güncellenmesine izin verin.

Yaşadığınız sorun, http çağrılarının eşzamansız olarak yapıldığının farkında olmamanızdır; bu, olabildiğince hemen bir değer alamayacağınız anlamına gelir. Örneğin,

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

Bunu aşmanın basit bir yolu var ve bir geri arama işlevi sağlamak.

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

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>

Bu, kısalık için iş mantığınızın bir kısmını ortadan kaldırdı ve kodu gerçekten test etmedim, ancak bunun gibi bir şey işe yarayacaktı. Ana konsept, denetleyiciden gelecekte daha sonra çağrılacak olan hizmete bir geri aramayı geçirmektir. NodeJS'ye aşina iseniz, bu aynı kavramdır.



0

Aynı çıkmaza girdim. Aşağıdakilerle sonuçlandım. Yani burada kapsam nesnesini fabrikaya enjekte etmiyorum, $ http hizmetinin döndürdüğü vaat kavramını kullanarak denetleyicinin kendisinde $ kapsamını ayarlıyorum .

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.