$ scopeを角度サービスfunction()に注入する


107

サービスがあります:

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の使用

回答:


183

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

スコープの目的は、アプリのプレゼンテーションとビジネスロジックを「接着」することです。を$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()ますが、より単純なサービスには「ショートカット」メソッドがあります(そのうちの2つはservice()およびfactory())。
すべてがサービスに「集約」されるため、どのメソッドを使用してもそれほど大きな違いはありません(サービスの要件がそのメソッドでカバーできる限り)。

ところで、providervs servicevs factoryはAngularの初心者にとって最もわかりにくい概念の1つですが、幸いなことに、物事を簡単にするためのリソース(ここではSO)がたくさんあります。(検索してみてください。)

(それがクリアされることを願っています-クリアされない場合はお知らせください。)


1
一つの質問。サービスと言いますが、コード例ではファクトリを使用しています。私は工場、サービス、プロバイダーの違いを理解し始めたばかりです。サービスを使用していたので、工場に行くことが最良の選択肢であることを確認したいだけです。あなたの例から多くを学びました。フィドルと非常に明確な説明をありがとう。
クリスフリシナ2014

3
@chrisFrisina:少し説明を付けて回答を更新しました。基本的に、それを使用しても、それほど大きな違いはありません- serviceまたはfactoryAngularサービスで終了します。それぞれがどのように機能するかを理解し、それがニーズに合っているかどうかを確認してください。
gkalpak 2014

いい投稿です!とても助かります!
Oni1 2014

ありがとう兄貴!ここでは類似した物質で素晴らしい記事ですstsc3000.github.io/blog/2013/10/26/...
Terafor

@ExpertSystem $scope.studentsajax呼び出しが完了しない場合、空になりますか?または$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;
                }
            });
        }());
    }]);

注意すべきことの1つは、サービス内で、studentsプロパティが表示されるためには、Serviceオブジェクトなどにある必要thisがあることです。

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

12

ええと(長いもの)... サービス内でのアクセスを主張する場合$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');
}]);

スコープはどのように破棄されますか?
JK。

9

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


あなたの言っていることが理解できます。ただし、私の場合は、多くのコントローラーがあり、非常によく似た$ watchesのセットを使用してそれらのスコープを構成したいと思います。どのように/どこでそれをしますか?現在、実際にスコープをパラメーターとして$ watchesを設定するサービスに渡します。
Moritz、2015年

@moritzは、セカンダリディレクティブ(スコープがfalseであるため、他のディレクティブで定義されたスコープを使用する)を実装している可能性があり、ウォッチやその他必要なもののバインディングを作成します。そうすれば、そのようなウォッチを定義する必要がある任意の場所でその他のディレクティブを使用できます。サービスにスコープを渡すことは確かにかなりひどいので:)(私を信じて、私はそこにいて、それをやった、最後に壁に頭を
ぶつけた

スコープを回すよりもずっといい音をする@TIMINeutronは、次のシナリオが出てきたときに試してみるよ!ありがとう!
モリッツ

承知しました。私はまだ自分自身を学んでいます、そしてこの特定の問題は私がこの特定の方法で最近取り組んだ問題であり、それは私にとって魅力のように働きました。
tfrascaroli 2016年

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に慣れている場合、これは同じ概念です。


このアプローチは推奨されません。Promise .thenメソッドからのコールバックがアンチパターンである理由を参照してください。
georgeawg

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.