Внедрение $scope в сервисную функцию angular()

У меня есть Сервис:

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

Но когда я звоню save(), у меня нет доступа к $scope, а get ReferenceError: $scope is not defined. Итак, логический шаг (для меня) заключается в предоставлении save() с помощью $scope, и поэтому я должен также предоставить/вставить его в service. Поэтому, если я так делаю:

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

Я получаю следующую ошибку:

Ошибка: [$ injector: unpr] Неизвестный поставщик: $scopeProvider < - $scope < - StudentService

Ссылка в ошибке (wow, которая является опрятной!) позволяет мне знать, что это связано с инжектором и может иметь отношение к порядку объявления js файлов. Я попытался переупорядочить их в index.html, но я думаю, что это нечто более простое, например, как я их ввожу.

Использование Angular -UI и Angular -UI-Router

Ответ 1

$scope, который вы видите впрыснутым в контроллеры, не является некоторой службой (например, остальной частью инъектируемого материала), но является объектом Scope. Многие объекты области могут быть созданы (обычно прототипно наследуется от родительской области). Корень всех областей - это $rootScope, и вы можете создать новую область с помощью метода $new() любой области (включая $rootScope).

Целью области является "склеить" презентацию и бизнес-логику вашего приложения. Не имеет смысла передавать $scope в службу.

Службы - это одноэлементные объекты, используемые (среди прочего) для обмена данными (например, среди нескольких контроллеров) и обычно инкапсулируют многократно используемые фрагменты кода (поскольку их можно вводить и предлагать свои "сервисы" в любой части вашего приложения, которое их нуждается: контроллеры, директивы, фильтры, другие сервисы и т.д.).

Я уверен, что различные подходы будут работать для вас. Один из них:
Поскольку StudentService отвечает за данные ученика, вы можете иметь StudentService сохранить массив учеников и позволить ему "делиться" с кем-то, кто может быть заинтересован (например, ваш $scope). Это имеет еще больший смысл, если есть другие виды/контроллеры/фильтры/службы, которые должны иметь доступ к этой информации (если их нет сейчас, не удивляйтесь, если они начнут появляться в ближайшее время).
Каждый раз, когда новый ученик добавляется (используя метод service save()), собственный массив учащихся будет обновляться, и каждый другой объект, совместно использующий этот массив, будет автоматически обновляться.

На основе описанного выше подхода ваш код может выглядеть так:

angular.module('cfd', [])

.factory('StudentService', ['$http', function ($http) {
    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 */
    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;
                }
            }
        }
    };

    /* Populate the students array with students from the server */
    $http.get(path).success(function (data) {
        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
            StudentService.save(student);
            // The $scope `students` array will be automatically updated
            // since it references the StudentService `students` array

            // Do some more $scope-specific stuff, 
            // e.g. show a notification 
        };
    }
]);

<суб > Одна вещь, которую вы должны быть осторожны при использовании этого подхода, - никогда не переустанавливать массив службы, потому что тогда любые другие компоненты (например, области действия) будут по-прежнему ссылаться на исходный массив, и ваше приложение сломается.
Например. для очистки массива в StudentService:

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

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

суб >

См. также эту короткую демонстрацию.


НЕСКОЛЬКО ОБНОВЛЕНИЕ:

Несколько слов, чтобы избежать путаницы, которая может возникнуть при разговоре об использовании службы, но не создавая ее с помощью функции service().

Цитирование документов на $provide:

Служба Angular - это одноэлементный объект, созданный службой factory. Эти сервисные заводы - это функции, которые, в свою очередь, создаются поставщиком услуг. поставщики услуг - это функции конструктора. При создании экземпляра они должны содержать свойство с именем $get, которое содержит функцию службы factory.
[...]
... служба $provide имеет дополнительные вспомогательные методы для регистрации служб без указания поставщика:

  • поставщик (поставщик) - регистрирует поставщика услуг с помощью $injector
  • константа (obj) - регистрирует значение/объект, к которому могут обращаться поставщики и службы.
  • value (obj) - регистрирует значение/объект, к которому могут обращаться только службы, а не провайдеры.
  • factory (fn) - регистрирует функцию factory службы fn, которая будет обернута в объект поставщика услуг, свойство $get будет содержать данную функцию factory.
  • service (класс) - регистрирует функцию-конструктор, класс, который будет обернут в объект поставщика услуг, свойство $get будет создавать экземпляр нового объекта с использованием данной функции конструктора.

В основном, то, что он говорит, заключается в том, что каждый сервис Angular зарегистрирован с использованием $provide.provider(), но для более простых сервисов существуют "короткие" методы (два из которых - service() и factory()).
Все это "сводится" к сервису, поэтому не имеет большого значения, какой метод вы используете (пока требования к вашему сервису могут быть покрыты этим методом).

BTW, provider vs service vs factory - одна из самых запутанных концепций для новых пользователей Angular, но, к счастью, есть много ресурсов (здесь, на SO), чтобы упростить задачу. (Просто найдите вокруг.)

(Я надеюсь, что это очистит - дайте мне знать, если это не так.)

Ответ 2

Вместо того, чтобы пытаться изменить $scope внутри службы, вы можете реализовать $watch внутри вашего контроллера, чтобы посмотреть свойство в своей службе для изменений, а затем обновить свойство на $scope. Вот пример, который вы можете попробовать в контроллере:

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

Следует отметить, что в вашем сервисе, чтобы свойство students было видимым, оно должно быть в объекте Service или this так:

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

Ответ 3

Хорошо (длинный)... если вы настаиваете иметь $scope доступ внутри службы, вы можете:

Создать услугу getter/setter

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

Вставить его и сохранить в нем область управления

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

Теперь найдите область внутри другой службы

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

Ответ 4

Службы - это синглтоны, и не логично вводить область в службу (что действительно так, вы не можете вводить область в службу). Вы можете передать область как параметр, но это также плохой выбор дизайна, потому что у вас будет область, редактируемая в нескольких местах, что затрудняет ее отладку. Код для работы с переменными области видимости должен идти в контроллере, а вызовы службы переходят в службу.

Ответ 5

Вы можете полностью отказаться от своей службы, но в вашем контроллере разрешить асинхронное обновление области.

Проблема, с которой вы сталкиваетесь, заключается в том, что вы не знаете, что HTTP-вызовы выполняются асинхронно, а это значит, что вы не получаете значение сразу, как можете. Например,

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

Там есть простой способ обойти это, и он будет обеспечивать функцию обратного вызова.

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

Форма:

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

Это сократило некоторые из вашей бизнес-логики для краткости, и я на самом деле не протестировал код, но что-то вроде этого будет работать. Основной концепцией является передача обратного вызова от контроллера к службе, которая будет вызвана позже в будущем. Если вы знакомы с NodeJS, это та же концепция.

Ответ 6

Попал в такое же затруднительное положение. Я закончил со следующим. Поэтому здесь я не вводя объект области в factory, но устанавливая $scope в самом контроллере, используя концепцию обещание, возвращенную $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);    
}());