Memasukkan $ scope ke dalam fungsi angular service ()

108

Saya memiliki Layanan:

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

Tetapi ketika saya menelepon save(), saya tidak memiliki akses ke $scope, dan mendapatkan ReferenceError: $scope is not defined. Jadi langkah logisnya (bagi saya), adalah menyediakan save () dengan $scope, dan dengan demikian saya juga harus menyediakan / menyuntikkannya ke file service. Jadi jika saya melakukannya seperti itu:

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

Saya mendapatkan kesalahan berikut:

Kesalahan: [$ injector: tidak] Penyedia tidak dikenal: $ scopeProvider <- $ scope <- StudentService

Tautan dalam kesalahan (wow itu rapi!) Memberi tahu saya itu terkait dengan injektor, dan mungkin ada hubungannya dengan urutan deklarasi file js. Saya telah mencoba mengatur ulang mereka di index.html, tetapi menurut saya ini adalah sesuatu yang lebih sederhana, seperti cara saya menyuntikkannya.

Menggunakan Angular-UI dan Angular-UI-Router

chris Frisina
sumber

Jawaban:

183

Yang $scopeAnda lihat disuntikkan ke pengontrol bukanlah layanan (seperti bagian lain yang dapat diinjeksi), tetapi merupakan objek Scope. Banyak objek lingkup dapat dibuat (biasanya secara prototipikal mewarisi dari lingkup induk). Akar dari semua cakupan adalah $rootScopedan Anda dapat membuat cakupan anak baru menggunakan $new()metode cakupan apa pun (termasuk $rootScope).

Tujuan dari Scope adalah untuk "merekatkan" presentasi dan logika bisnis aplikasi Anda. Tidak masuk akal untuk memasukkan a $scopeke dalam sebuah layanan.

Layanan adalah objek tunggal yang digunakan (antara lain) untuk berbagi data (misalnya di antara beberapa pengontrol) dan umumnya merangkum potongan kode yang dapat digunakan kembali (karena mereka dapat dimasukkan dan menawarkan "layanan" mereka di bagian mana pun dari aplikasi Anda yang membutuhkannya: pengontrol, arahan, filter, layanan lain, dll.).

Saya yakin, berbagai pendekatan akan berhasil untuk Anda. Salah satunya adalah:
Karena StudentServicebertugas menangani data siswa, Anda dapat StudentServicemenyimpan sejumlah siswa dan membiarkannya "membagikan" dengan siapa pun yang mungkin tertarik (misalnya Anda $scope). Ini bahkan lebih masuk akal, jika ada tampilan / pengontrol / filter / layanan lain yang perlu memiliki akses ke info itu (jika tidak ada saat ini, jangan kaget jika mereka segera mulai bermunculan).
Setiap kali siswa baru ditambahkan (menggunakan metode layanan save()), larik siswa layanan itu sendiri akan diperbarui dan setiap objek lain yang berbagi larik itu akan diperbarui secara otomatis juga.

Berdasarkan pendekatan yang dijelaskan di atas, kode Anda akan terlihat seperti ini:

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

Satu hal yang harus Anda perhatikan saat menggunakan pendekatan ini adalah jangan pernah menetapkan ulang larik layanan, karena komponen lain (misalnya cakupan) akan tetap merujuk ke larik asli dan aplikasi Anda akan rusak.
Misalnya untuk menghapus array di StudentService:

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

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

Lihat juga, demo singkat ini .


UPDATE KECIL:

Beberapa kata untuk menghindari kebingungan yang mungkin timbul saat berbicara tentang menggunakan layanan, tetapi tidak membuatnya dengan service()fungsinya.

Mengutip dokumen tentang$provide :

Sebuah sudut layanan adalah objek tunggal yang diciptakan oleh pabrik layanan . Pabrik layanan ini adalah fungsi yang, pada gilirannya, dibuat oleh penyedia layanan . The penyedia layanan adalah fungsi konstruktor. Ketika dibuat, mereka harus berisi properti yang dipanggil $get, yang memegang fungsi pabrik layanan .
[...]
... $providelayanan memiliki metode pembantu tambahan untuk mendaftarkan layanan tanpa menentukan penyedia:

  • provider (provider) - mendaftarkan penyedia layanan dengan $ injector
  • konstan (obj) - mendaftarkan nilai / objek yang dapat diakses oleh penyedia dan layanan.
  • nilai (obj) - mendaftarkan nilai / objek yang hanya dapat diakses oleh layanan, bukan penyedia.
  • factory (fn) - mendaftarkan fungsi pabrik layanan, fn, yang akan dibungkus dalam objek penyedia layanan, yang properti $ getnya akan berisi fungsi pabrik yang diberikan.
  • service (class) - meregistrasikan fungsi konstruktor, kelas yang akan dibungkus dalam objek penyedia layanan, yang properti $ getnya akan membuat instance objek baru menggunakan fungsi konstruktor yang diberikan.

Pada dasarnya, apa yang dikatakan adalah bahwa setiap layanan Angular terdaftar menggunakan $provide.provider(), tetapi ada metode "pintasan" untuk layanan yang lebih sederhana (dua di antaranya adalah service()dan factory()).
Semuanya "bermuara" pada layanan, jadi tidak ada bedanya metode mana yang Anda gunakan (selama persyaratan untuk layanan Anda dapat dicakup oleh metode itu).

BTW, providervs servicevs factoryadalah salah satu konsep yang paling membingungkan untuk pendatang baru Angular, tetapi untungnya ada banyak sumber daya (di SO) untuk mempermudah. (Cari saja.)

(Saya harap itu menyelesaikannya - beri tahu saya jika tidak.)

gkalpak.dll
sumber
1
Satu pertanyaan. Anda mengatakan layanan, tetapi contoh kode Anda menggunakan pabrik. Saya baru mulai memahami perbedaan antara pabrik, jasa, dan penyedia, hanya ingin memastikan bahwa pergi dengan pabrik adalah pilihan terbaik, karena saya menggunakan jasa. Belajar banyak dari teladan Anda. Terima kasih untuk biola dan penjelasan yang SANGAT jelas.
chris Frisina
3
@chrisFrisina: Memperbarui jawaban dengan sedikit penjelasan. Pada dasarnya, tidak ada bedanya jika Anda menggunakan serviceatau factory- Anda akan mengakhiri Anda dengan dan layanan Angular . Pastikan Anda memahami cara kerja masing-masing dan apakah itu sesuai dengan kebutuhan Anda.
gkalpak
Pos yang bagus! Ini sangat membantu saya!
Oni1
Makasih bro! berikut adalah artikel yang bagus tentang masalah serupa stsc3000.github.io/blog/2013/10/26/…
Terafor
@ExpertSystem Apakah $scope.studentsakan kosong, jika panggilan ajax belum selesai? Atau $scope.studentsakan terisi sebagian, jika blok kode ini sedang bekerja? students.push(student);
Yc Zhang
18

Daripada mencoba mengubah $scopedalam layanan, Anda dapat mengimplementasikan a $watchdalam pengontrol Anda untuk mengawasi properti di layanan Anda untuk perubahan dan kemudian memperbarui properti di $scope. Berikut adalah contoh yang dapat Anda coba di pengontrol:

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

Satu hal yang perlu diperhatikan adalah bahwa dalam layanan Anda, agar studentsproperti dapat terlihat, properti tersebut harus berada di objek Service atau thissemacamnya:

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

Nah (yang panjang) ... jika Anda bersikeras untuk memiliki $scopeakses di dalam layanan, Anda dapat:

Buat layanan pengambil / penyetel

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

Masukkan dan simpan lingkup pengontrol di dalamnya

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

Sekarang, dapatkan cakupan di dalam layanan lain

ngapp.factory('getRoute', ['Scopes', '$http', function(Scopes, $http){
  // there you are
  var $scope = Scopes.get('myCtrl');
}]);
Jonatas Walker
sumber
Bagaimana cara menghancurkannya?
JK.
9

Layanan adalah lajang, dan tidak logis untuk cakupan yang akan dimasukkan dalam layanan (yang memang benar, Anda tidak dapat memasukkan cakupan dalam layanan). Anda dapat meneruskan cakupan sebagai parameter, tetapi itu juga merupakan pilihan desain yang buruk, karena Anda akan memiliki cakupan yang sedang diedit di banyak tempat, sehingga menyulitkan proses debug. Kode untuk menangani variabel lingkup harus masuk ke pengontrol, dan panggilan layanan masuk ke layanan.

Ermin Dedovic
sumber
Saya mengerti apa yang Anda katakan. Namun, dalam kasus saya, saya memiliki banyak pengontrol dan saya ingin mengonfigurasi cakupan mereka dengan set $ jam tangan yang sangat mirip. Bagaimana / di mana Anda akan melakukan itu? Saat ini, saya memang meneruskan cakupan sebagai parameter ke layanan yang menyetel $ jam tangan.
moritz
@moritz mungkin mengimplementasikan arahan sekunder (yang memiliki scope: false, sehingga ia menggunakan ruang lingkup yang ditentukan oleh arahan lain) dan yang membuat binding dari watchess, serta hal lain yang Anda butuhkan. Dengan cara itu Anda dapat menggunakan arahan lain itu di tempat mana pun yang Anda perlukan untuk menentukan jam tangan tersebut. Karena meneruskan cakupan ke layanan memang cukup mengerikan :) (percayalah, saya pernah ke sana, melakukan itu, membenturkan kepala saya ke dinding pada akhirnya)
tfrascaroli
@TIMINeutron yang terdengar jauh lebih baik daripada melewati ruang lingkup, saya akan mencoba lain kali skenario muncul! Terima kasih!
moritz
Tentu. Saya masih belajar sendiri, dan masalah khusus ini adalah masalah yang baru-baru ini saya tangani dengan cara khusus ini, dan itu bekerja seperti pesona bagi saya.
tfrascaroli
3

Anda dapat membuat layanan Anda sama sekali tidak mengetahui cakupan, tetapi di pengontrol Anda mengizinkan cakupan untuk diperbarui secara asinkron.

Masalah yang Anda hadapi adalah karena Anda tidak menyadari bahwa panggilan http dibuat secara asinkron, yang berarti Anda tidak mendapatkan nilai secepat mungkin. Misalnya,

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

Ada cara sederhana untuk menyiasatinya dan itu dengan menyediakan fungsi 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
    })
  }
}]);

Formulir:

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

Ini menghapus beberapa logika bisnis Anda karena singkatnya dan saya belum benar-benar menguji kodenya, tetapi sesuatu seperti ini akan berhasil. Konsep utamanya adalah meneruskan callback dari pengontrol ke layanan yang akan dipanggil nanti. Jika Anda terbiasa dengan NodeJS, ini adalah konsep yang sama.

2upmedia
sumber
Pendekatan ini tidak disarankan. Lihat Mengapa Callback dari .thenMetode Janji merupakan Anti-Pola .
georgeawg
0

Masuk ke dalam kesulitan yang sama. Saya berakhir dengan yang berikut ini. Jadi di sini saya tidak menyuntikkan objek scope ke pabrik, tetapi menyetel $ scope di controller itu sendiri menggunakan konsep promise yang dikembalikan oleh layanan $ 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);    
}());
VivekDev
sumber