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


108

我有服务:

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 is not defined。因此,对我而言,逻辑上的步骤是为save()提供$scope,因此我还必须将其提供/注入到service。因此,如果我这样做:

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

我收到以下错误:

错误:[$ injector:unpr]未知提供程序:$ scopeProvider <-$ scope <-StudentService

错误中的链接(哇,很干净!)让我知道它与注入器相关,并且可能与js文件的声明顺序有关。我尝试在中对它们进行重新排序index.html,但是我认为这更简单,例如我注入它们的方式。

使用Angular-UI和Angular-UI-Router

Answers:


183

$scope你看到被注入控制器是不是有些服务(如的注射材料的其余部分),而是一个范围对象。可以创建许多作用域对象(通常是从父作用域继承的原型)。所有作用域的根都是$rootScope,您可以使用$new()任何作用域(包括$rootScope)的方法来创建新的子作用域。

范围的目的是将应用程序的表示和业务逻辑“粘合在一起”。将a传递$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.
        });
      };
    }
]);

使用此方法时,应注意的一件事是永远不要重新分配服务的数组,因为那样的话,其他任何组件(例如作用域)仍将引用原始数组,并且您的应用程序将中断。
例如清除数组StudentService

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

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

另请参见此简短演示


小更新:

为了避免在谈论使用服务时出现混乱,而不是通过service()功能创建它,请说几句。

引用以下文档$provide

Angular 服务是由服务工厂创建的单例对象。这些服务工厂是由服务提供商创建的功能。该服务提供商是构造函数。实例化后,它们必须包含一个名为的属性$get,该属性包含服务工厂功能。
[...]
...的$provide服务有额外的辅助方法,而不指定提供商注册服务:

  • provider(provider) -向$ injector注册服务提供商
  • constant(obj) -注册可以由提供者和服务访问的值/对象。
  • value(obj) -注册一个只能由服务而非提供者访问的值/对象。
  • factory(fn) -注册服务工厂函数fn,该函数将包装在服务提供程序对象中,该对象的$ get属性将包含给定的工厂函数。
  • service(class) -注册一个构造函数,该类将包装在服务提供者对象中,该类的$ get属性将使用给定的构造函数实例化一个新对象。

基本上,它说的是每个Angular服务都是使用来注册的$provide.provider(),但是对于更简单的服务,有“捷径”方法(其中两个是service()factory())。
所有这些都“归结为”服务,因此使用哪种方法并没有多大区别(只要该方法可以满足服务要求)。

顺便说一句,providervs vs servicevs factory是Angular新手最容易混淆的概念之一,但幸运的是,有很多资源(在SO上)使事情变得更容易。(只需四处搜索。)

(我希望这可以解决-如果不能,请告诉我。)


1
一个问题。您说的是服务,但您的代码示例使用工厂。我刚刚开始了解工厂,服务和提供程序之间的区别,只是想确保与工厂合作是最佳选择,因为我正在使用服务。从您的例子中学到了很多。感谢您的小提琴和非常清楚的解释。
克里斯·弗里西纳

3
@chrisFrisina:更新了答案,并做了一些解释。基本上,如果您使用servicefactory-您将以and Angular service结尾没有太大区别。只要确保你明白如何它的每一件作品,如果适合您的需要。
gkalpak

好贴!它对我有很大帮助!
2014年

多谢兄弟!这是一篇关于类似问题的好文章stsc3000.github.io/blog/2013/10/26/…–
Terafor

@@ ExpertSystem $scope.students如果ajax调用未完成,将为空吗?还是$scope.students要部分填充(如果此代码块正在进行中)? students.push(student);
Yc Zhang

18

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

12

好吧(很长)...如果您坚持要在$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');
}]);

范围如何被破坏?
JK。

9

服务是单例的,将范围注入服务中是不合逻辑的(确实如此,您不能将范围注入服务中)。您可以将范围作为参数传递,但这也不是一个好的设计选择,因为您可能会在多个位置编辑范围,这使得调试变得很困难。处理范围变量的代码应该放在控制器中,而服务调用则转到该服务。


我明白你在说什么。但是,就我而言,我有许多控制器,我想用一组非常相似的$ watches配置它们的作用域。您将如何/在哪里做?目前,我确实将范围作为参数传递给设置$ watches的服务。
莫里茨

@moritz可能实现辅助指令(该指令的作用域为:false,因此它使用其他指令定义的范围),并且该辅助指令可以构成手表的绑定以及您需要的其他任何东西。这样,您可以在需要定义此类手表的任何地方使用该其他指令。因为将范围传递给服务确实确实很糟糕:)(相信我,我去过那儿,最后把我的头撞在墙上)
tfrascaroli

@TIMINeutron听起来比通过示波器要好得多,我会在下一次出现这种情况时尝试!谢谢!
莫里茨

当然。我仍在学习自己,这个特殊的问题是我最近以这种特殊方式解决的,对我来说就像一个魅力。
tfrascaroli

3

您可以使服务完全不了解作用域,但是在您的控制器中允许异步更新作用域。

您遇到的问题是因为您没有意识到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,则是相同的概念。



0

陷入了同样的困境。我得出以下结论。因此,这里我不是将范围对象注入工厂,而是使用$ http服务返回的promise概念在控制器本身中设置$ scope

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.