Chèn $ scope vào một hàm dịch vụ góc ()


108

Tôi có một Dịch vụ:

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

Nhưng khi tôi gọi save(), tôi không có quyền truy cập $scopevà nhận được ReferenceError: $scope is not defined. Vì vậy, bước hợp lý (đối với tôi), là cung cấp save () với $scope, và do đó tôi cũng phải cung cấp / tiêm nó vào service. Vì vậy, nếu tôi làm điều đó như vậy:

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

Tôi nhận được lỗi sau đây:

Lỗi: [$ injectionor: unr] Nhà cung cấp không xác định: $ scopeProvider <- $ scope <- StudentService

Liên kết trong lỗi (thật tuyệt!) Cho tôi biết nó có liên quan đến bộ phun và có thể liên quan đến thứ tự khai báo các tệp js. Tôi đã thử sắp xếp lại thứ tự chúng trong index.html, nhưng tôi nghĩ nó là một cái gì đó đơn giản hơn, chẳng hạn như cách tôi tiêm chúng.

Sử dụng Angular-UI và Angular-UI-Router

Câu trả lời:


183

Cái $scopemà bạn thấy được đưa vào bộ điều khiển không phải là một số dịch vụ (giống như phần còn lại của nội dung có thể tiêm), mà là một đối tượng Phạm vi. Nhiều đối tượng phạm vi có thể được tạo (thường là kế thừa nguyên mẫu từ phạm vi cha). Gốc của tất cả các phạm vi là $rootScopevà bạn có thể tạo một phạm vi con mới bằng cách sử dụng $new()phương thức của bất kỳ phạm vi nào (bao gồm cả $rootScope).

Mục đích của Phạm vi là "kết dính" bản trình bày và logic kinh doanh của ứng dụng của bạn. Không có nhiều ý nghĩa khi chuyển $scopemột dịch vụ vào một dịch vụ.

Dịch vụ là các đối tượng đơn lẻ được sử dụng (trong số những thứ khác) để chia sẻ dữ liệu (ví dụ: giữa một số bộ điều khiển) và thường đóng gói các đoạn mã có thể sử dụng lại (vì chúng có thể được đưa vào và cung cấp "dịch vụ" của chúng trong bất kỳ phần nào của ứng dụng cần chúng: bộ điều khiển, chỉ thị, bộ lọc, các dịch vụ khác, v.v.).

Tôi chắc chắn, nhiều cách tiếp cận khác nhau sẽ phù hợp với bạn. Một là:
StudentServicephụ trách xử lý dữ liệu sinh viên, bạn có thể có StudentServicemột loạt các sinh viên và để nó "chia sẻ" nó với bất kỳ ai có thể quan tâm (ví dụ: của bạn $scope). Điều này thậm chí còn có ý nghĩa hơn, nếu có các chế độ xem / bộ điều khiển / bộ lọc / dịch vụ khác cần có quyền truy cập vào thông tin đó (nếu không có bất kỳ thông tin nào ngay bây giờ, đừng ngạc nhiên nếu chúng bắt đầu xuất hiện sớm).
Mỗi khi một sinh viên mới được thêm vào (sử dụng save()phương thức của dịch vụ ), mảng sinh viên của chính dịch vụ sẽ được cập nhật và mọi đối tượng khác chia sẻ mảng đó cũng sẽ được cập nhật tự động.

Dựa trên cách tiếp cận được mô tả ở trên, mã của bạn có thể trông giống như sau:

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

Một điều bạn nên cẩn thận khi sử dụng phương pháp này là không bao giờ gán lại mảng của dịch vụ, vì khi đó bất kỳ thành phần nào khác (ví dụ: phạm vi) sẽ vẫn tham chiếu đến mảng ban đầu và ứng dụng của bạn sẽ bị hỏng.
Ví dụ: xóa mảng trong StudentService:

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

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

Ngoài ra, hãy xem bản demo ngắn này .


CẬP NHẬT ÍT NHẤT:

Một vài từ để tránh sự nhầm lẫn có thể phát sinh khi nói về việc sử dụng một dịch vụ, nhưng không tạo ra nó với service()chức năng.

Trích dẫn các tài liệu về$provide :

Một dịch vụ Angular là một đối tượng singleton được tạo bởi một nhà máy dịch vụ . Các nhà máy dịch vụ này là các chức năng do một nhà cung cấp dịch vụ tạo ra . Các nhà cung cấp dịch vụ là các hàm khởi tạo. Khi được khởi tạo, chúng phải chứa một thuộc tính được gọi $get, giữ chức năng nhà máy dịch vụ .
[...]
... $providedịch vụ có thêm các phương thức trợ giúp để đăng ký dịch vụ mà không cần chỉ định nhà cung cấp:

  • nhà cung cấp (nhà cung cấp) - đăng ký nhà cung cấp dịch vụ với $ injection
  • hằng (obj) - đăng ký một giá trị / đối tượng có thể được truy cập bởi các nhà cung cấp và dịch vụ.
  • value (obj) - đăng ký một giá trị / đối tượng chỉ có thể được truy cập bởi các dịch vụ, không phải nhà cung cấp.
  • factory (fn) - đăng ký một hàm nhà máy dịch vụ, fn, sẽ được bao bọc trong một đối tượng nhà cung cấp dịch vụ, có thuộc tính $ get sẽ chứa hàm nhà máy đã cho.
  • service (class) - đăng ký một hàm tạo, lớp sẽ được bao bọc trong một đối tượng cung cấp dịch vụ, có thuộc tính $ get sẽ khởi tạo một đối tượng mới bằng cách sử dụng hàm khởi tạo đã cho.

Về cơ bản, những gì nó nói là mọi dịch vụ Angular đều được đăng ký bằng cách sử dụng $provide.provider(), nhưng có các phương thức "phím tắt" cho các dịch vụ đơn giản hơn (hai trong số đó là service()factory()).
Tất cả đều "tóm gọn" vào một dịch vụ, vì vậy không có gì khác biệt nhiều so với phương pháp bạn sử dụng (miễn là phương pháp đó có thể đáp ứng các yêu cầu đối với dịch vụ của bạn).

BTW, providervs servicevs factorylà một trong những khái niệm khó hiểu nhất đối với những người mới đến với Angular, nhưng may mắn thay, có rất nhiều tài nguyên (ở đây trên SO) để làm cho mọi thứ dễ dàng hơn. (Chỉ cần tìm kiếm xung quanh.)

(Tôi hy vọng điều đó sẽ rõ ràng - hãy cho tôi biết nếu không.)


1
Một câu hỏi. Bạn nói dịch vụ, nhưng ví dụ mã của bạn sử dụng nhà máy. Tôi mới bắt đầu hiểu sự khác biệt giữa nhà máy, dịch vụ và nhà cung cấp, chỉ muốn chắc chắn rằng đi cùng nhà máy là lựa chọn tốt nhất, vì tôi đã sử dụng dịch vụ. Học được rất nhiều từ ví dụ của bạn. Cảm ơn vì sự giải thích RẤT rõ ràng và RẤT rõ ràng.
chris Frisina

3
@chrisFrisina: Đã cập nhật câu trả lời với một chút giải thích. Về cơ bản, nó không tạo ra nhiều khác biệt nếu bạn sử dụng servicehoặc factory- bạn sẽ kết thúc u bằng và dịch vụ Angular . Chỉ cần đảm bảo rằng bạn hiểu cách hoạt động của từng loại và nếu nó phù hợp với nhu cầu của bạn.
gkalpak

Bài đăng hay! Nó giúp tôi rất nhiều!
Oni1

Cảm ơn nhé người anh em! đây là một bài viết hay về vấn đề tương tự stsc3000.github.io/blog/2013/10/26/…
Terafor

@ExpertSystem Sẽ $scope.studentstrống, nếu cuộc gọi ajax không kết thúc? Hoặc $scope.studentssẽ được lấp đầy một phần, nếu khối mã này đang hoạt động? students.push(student);
Yc Zhang

18

Thay vì cố gắng sửa đổi $scopebên trong dịch vụ, bạn có thể triển khai $watchbên trong bộ điều khiển của mình để theo dõi một thuộc tính trên dịch vụ của bạn để biết các thay đổi và sau đó cập nhật một thuộc tính trên $scope. Đây là một ví dụ bạn có thể thử trong bộ điều khiển:

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

Một điều cần lưu ý là trong dịch vụ của bạn, để thuộc studentstính hiển thị, nó cần phải nằm trên đối tượng Dịch vụ hoặc thistương tự như vậy:

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

12

Vâng (một con đường dài) ... nếu bạn khăng khăng phải có $scopequyền truy cập bên trong một dịch vụ, bạn có thể:

Tạo dịch vụ getter / setter

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

Tiêm nó và lưu trữ phạm vi bộ điều khiển trong đó

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

Bây giờ, hãy lấy phạm vi bên trong một dịch vụ khác

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

Các phạm vi bị phá hủy như thế nào?
JK.

9

Các dịch vụ là các singleleton và sẽ không hợp lý khi một phạm vi được đưa vào trong dịch vụ (thực tế là bạn không thể đưa phạm vi vào trong dịch vụ). Bạn có thể chuyển phạm vi dưới dạng tham số, nhưng đó cũng là một lựa chọn thiết kế tồi, vì bạn sẽ có phạm vi được chỉnh sửa ở nhiều nơi, gây khó khăn cho việc gỡ lỗi. Mã để xử lý các biến phạm vi nên đi trong bộ điều khiển và các lệnh gọi dịch vụ sẽ đi đến dịch vụ.


Tôi hiểu những gì bạn đang nói. Tuy nhiên, trong trường hợp của tôi, tôi có nhiều bộ điều khiển và tôi muốn định cấu hình phạm vi của chúng với một bộ đồng hồ $ rất giống nhau. Bạn sẽ làm điều đó bằng cách nào / ở đâu? Hiện tại, tôi thực sự chuyển phạm vi dưới dạng tham số cho một dịch vụ đặt đồng hồ $.
moritz

@moritz có thể triển khai một chỉ thị phụ (một chỉ thị có phạm vi: false, vì vậy nó sử dụng phạm vi được xác định bởi các chỉ thị khác) và chỉ thị đó tạo các ràng buộc của watchess, cũng như bất kỳ thứ gì khác bạn cần. Bằng cách đó, bạn có thể sử dụng chỉ thị khác ở bất kỳ nơi nào bạn cần để xác định những chiếc đồng hồ như vậy. Bởi vì chuyển phạm vi sang một dịch vụ thực sự là khá khủng khiếp :) (tin tôi đi, tôi đã ở đó, làm điều đó, cuối cùng đập đầu vào tường)
tfrascaroli

@TIMINeutron nghe có vẻ tốt hơn nhiều so với việc vượt qua phạm vi, tôi sẽ thử điều đó vào lần sau khi kịch bản xuất hiện! Cảm ơn!
moritz

Chắc chắn rồi. Tôi vẫn đang học hỏi bản thân mình, và vấn đề cụ thể này là vấn đề gần đây tôi đã giải quyết theo cách cụ thể này, và nó hoạt động như một cái duyên đối với tôi.
tfrascaroli

3

Bạn có thể làm cho dịch vụ của mình hoàn toàn không biết về phạm vi, nhưng trong bộ điều khiển của bạn cho phép phạm vi được cập nhật không đồng bộ.

Vấn đề bạn đang gặp phải là do bạn không biết rằng các cuộc gọi http được thực hiện không đồng bộ, có nghĩa là bạn không nhận được giá trị ngay lập tức như bạn có thể. Ví dụ,

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

Có một cách đơn giản để giải quyết vấn đề này và đó là cung cấp một hàm gọi lại.

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

Hình thức:

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

Điều này đã loại bỏ một số logic kinh doanh của bạn cho ngắn gọn và tôi chưa thực sự kiểm tra mã, nhưng một cái gì đó như thế này sẽ hoạt động. Khái niệm chính là chuyển một lệnh gọi lại từ bộ điều khiển tới dịch vụ được gọi sau này trong tương lai. Nếu bạn đã quen với NodeJS thì đây là khái niệm tương tự.


Cách tiếp cận này không được khuyến khích. Xem Tại sao Callbacks từ Promise .thenMethod lại là Anti-Pattern .
georgeawg

0

Gặp phải tình trạng khó khăn tương tự. Tôi đã kết thúc với những điều sau đây. Vì vậy, ở đây tôi không đưa đối tượng phạm vi vào nhà máy, mà đặt phạm vi $ trong chính bộ điều khiển bằng cách sử dụng khái niệm lời hứa do dịch vụ $ http trả về .

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.