web-dev-qa-db-ja.com

角度範囲関数への$ scopeの注入()

私はサービスをしています:

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の使い方

105
chris Frisina

あなたがコントローラに注入されているのを見ることができる$scopeは、(残りの注入可能なもののような)なんらかのサービスではありませんが、Scopeオブジェクトです。多くのスコープオブジェクトを作成することができます(通常はプロトタイプで親スコープから継承します)。すべてのスコープのルートは$rootScopeであり、任意のスコープ($rootScopeを含む)の$new()メソッドを使用して新しい子スコープを作成できます。

Scopeの目的は、アプリケーションのプレゼンテーションとビジネスロジックを「結合」することです。 $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.
        });
      };
    }
]);

この方法を使用するときに注意する必要があることの1つは、サービスの配列を再割り当てしないことです。その場合、他のコンポーネント(スコープなど)は依然として元の配列を参照しているため、アプリは壊れます。
例えば。 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) - サービスプロバイダオブジェクトにラップされるコンストラクタ関数classを登録します。このクラスの$ getプロパティは、を使用して新しいオブジェクトをインスタンス化します。与えられたコンストラクタ関数.

基本的には、すべてのAngularサービスは$provide.provider()を使用して登録されるということですが、より単純なサービスには「ショートカット」メソッドがあります(そのうちの2つはservice()およびfactory())。
それはすべてサービスに「煮詰まる」ので、あなたがどの方法を使用するかはそれほど大きな違いはありません(あなたのサービスの要件がその方法によってカバーされることができる限り)。

ところで、provider vs service vs factoryは、Angular初心者にとって最も分かりにくい概念の1つですが、幸い、物事を容易にするために十分なリソース(ここではSO)があります。 (周りを検索してください)

(私はそれがそれを解決することを願っています - それがそうでないかどうか私に知らせてください。)

181
gkalpak

サービス内で$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;
});
17
Keith Morris

よく(長い)... 主張するサービス内で$scopeにアクセスするには、次のようにします。

ゲッター/セッターサービスを作成する

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');
}]);
12
Jonatas Walker

サービスはシングルトンであり、スコープがサービスにインジェクトされるのは論理的ではありません(実際には、スコープをインサービスにインジェクトすることはできません)。スコープをパラメータとして渡すこともできますが、スコープを複数の場所で編集することになり、デバッグが困難になるため、これも設計上の選択としては不適切です。スコープ変数を扱うためのコードはコントローラに入れるべきです、そして、サービス呼び出しはサービスに行きます。

8
Ermin Dedovic

あなたはあなたのサービスをスコープに全く気付かないようにすることができます、しかしあなたのコントローラでスコープが非同期に更新されることを可能にします。

あなたが抱えている問題は、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に慣れているならば、これは同じ概念です。

3
2upmedia

同じ苦境に陥った。私は次のようになりました。そのため、ここではスコープオブジェクトをファクトリにインジェクトしていませんが、 promise の概念を使用して $ scope をコントローラ自体に設定します。 $ http serviceによる。

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

スコープ変数を扱うためのコードはコントローラに入れるべきです、そして、サービス呼び出しはサービスに行きます。

$rootScopeおよび$rootScope.$broadcastを使用する目的で$rootScope.$onを注入することができます。

それ以外の場合は$rootScopeを挿入しないでください。見る

0
georgeawg