Angularjs 将$scope注入到角度服务函数()中

Angularjs 将$scope注入到角度服务函数()中,angularjs,angular-ui,angular-ui-router,Angularjs,Angular Ui,Angular Ui Router,我有一项服务: 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.da

我有一项服务:

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
,并获取
ReferenceError:$scope未定义。因此,(对我来说)合乎逻辑的步骤是为save()提供
$scope
,因此我还必须向
服务提供/注入它。如果我这样做:

  .service('StudentService', [ '$http', '$scope',
                      function ($http, $scope) {
我得到以下错误:


错误:[$injector:unpr]未知提供程序:$scopeProvider服务是单例的,在服务中注入作用域是不符合逻辑的(事实上,您不能在服务中注入作用域)。您可以将范围作为参数传递,但这也是一个糟糕的设计选择,因为您将在多个位置编辑范围,从而使调试变得困难。处理作用域变量的代码应该进入控制器,服务调用进入服务。

您可以在控制器中实现$watch,以观察服务上的属性变化,然后更新$scope上的属性,而不是试图修改服务中的$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
属性可见,它需要位于服务对象或
上,如下所示:

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

您看到被注入控制器的
$scope
不是某种服务(像其他可注入的东西一样),而是一个scope对象。可以创建许多范围对象(通常是从父范围继承的)。所有作用域的根是
$rootScope
,您可以使用任何作用域(包括
$rootScope
)的
$new()
方法创建新的子作用域

范围的目的是“粘合”应用程序的表示和业务逻辑。将
$scope
传递到服务中没有多大意义

服务是用于(除其他外)共享数据(例如,在多个控制器之间)的单一对象,通常封装可重用的代码段(因为它们可以被注入并在应用程序中需要它们的任何部分提供“服务”:控制器、指令、过滤器、其他服务等)

我相信,各种方法都会对你有用。一个是:
由于
StudentService
负责处理学生数据,因此您可以让
StudentService
保留一个学生数组,并让它与可能感兴趣的任何人(例如您的
$scope
)共享。如果有其他视图/控制器/过滤器/服务需要访问该信息,这就更有意义了(如果现在没有,如果它们很快就会出现,请不要感到惊讶)。
每次添加一个新的学生(使用服务的
save()
方法),该服务自己的学生数组将被更新,共享该数组的所有其他对象也将自动更新

基于上述方法,您的代码可能如下所示:

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.
        });
      };
    }
]);
另请参见此


小更新:

在谈论使用服务,但不使用
service()
函数创建服务时,请用几句话避免可能出现的混淆

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

角度服务是由服务工厂创建的单例对象。这些服务工厂是由服务提供商创建的功能。服务提供者是构造函数。实例化时,它们必须包含名为
$get
的属性,该属性包含服务工厂功能。
[…]
$provide
服务具有其他帮助器方法来注册服务,而无需指定提供程序:

  • 提供者(提供者)-向$injector注册服务提供者
  • 常量(obj)-注册提供者和服务可以访问的值/对象
  • 值(obj)-注册只能由服务而不是提供者访问的值/对象
  • 工厂(fn)-注册服务工厂函数fn,该函数将包装在服务提供者对象中,其$get属性将包含给定的工厂函数
  • 服务(类)-注册一个构造函数,该类将封装在服务提供者对象中,其$get属性将使用给定的构造函数实例化一个新对象
基本上,它说的是每个角度服务都是使用
$provide.provider()
注册的,但是对于更简单的服务有“快捷”方法(其中两种是
service()
factory()
)。
这一切都“归结”为一项服务,因此使用哪种方法没有多大区别(只要该方法能够满足您的服务需求)

顺便说一句,
provider
vs
service
vs
factory
是新来者最容易混淆的概念之一,但幸运的是,有大量的资源(这里就这样)让事情变得更简单。(随便看看。)


(我希望这会清除它-如果没有,请告诉我。)

您可以让您的服务完全不知道作用域,但在控制器中允许异步更新作用域

您遇到的问题是,您不知道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>
{{message}
名称:
电子邮件:
性别:男 女性
(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);    
}());
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');
}]);