1. ホーム
  2. angularjs

[解決済み] AngularJS : サービスプロパティへの正しいバインディングの方法

2022-04-25 18:18:25

質問

AngularJSでサービスのプロパティにバインドする方法について、ベストプラクティスを探しています。

AngularJSを使用して作成されたサービスのプロパティにバインドする方法を理解するために、複数の例を通して作業しました。

以下に、サービスのプロパティにバインドする方法の例を2つ示しますが、どちらも動作します。 最初の例では基本的なバインディングを使用し、2番目の例では $scope.$watch を使用してサービスのプロパティにバインドしています。

サービス内のプロパティにバインドする場合、これらの例のどちらかが好ましいのでしょうか。それとも、私が知らない他のオプションが推奨されるのでしょうか。

これらの例の前提は、サービスが5秒ごとにそのプロパティ「lastUpdated」と「calls」を更新することです。 サービスのプロパティが更新されると、ビューはこれらの変更を反映する必要があります。 これらの例はどちらも正常に動作していますが、もっと良い方法があるのでしょうか?

基本的なバインディング

以下のコードは、ここで表示・実行することができます。 http://plnkr.co/edit/d3c16z

<html>
<body ng-app="ServiceNotification" >

    <div ng-controller="TimerCtrl1" style="border-style:dotted"> 
        TimerCtrl1 <br/>
        Last Updated: {{timerData.lastUpdated}}<br/>
        Last Updated: {{timerData.calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.timerData = Timer.data;
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 5000);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>
</html>

サービスプロパティへのバインディングを解決するもう一つの方法は、コントローラで$scope.$watchを使用することです。

スコープ.$watch

次のコードは、ここで表示・実行できます。 http://plnkr.co/edit/dSBlC9

<html>
<body ng-app="ServiceNotification">
    <div style="border-style:dotted" ng-controller="TimerCtrl1">
        TimerCtrl1<br/>
        Last Updated: {{lastUpdated}}<br/>
        Last Updated: {{calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.$watch(function () { return Timer.data.lastUpdated; },
                function (value) {
                    console.log("In $watch - lastUpdated:" + value);
                    $scope.lastUpdated = value;
                }
            );

            $scope.$watch(function () { return Timer.data.calls; },
                function (value) {
                    console.log("In $watch - calls:" + value);
                    $scope.calls = value;
                }
            );
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 5000);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>
</html>

サービス内で$rootscope.$broadcast、コントローラ内で$root.$onを使用できることは承知していますが、私が作成した$broadcast/$onを使用した他の例では、最初のブロードキャストはコントローラで捕捉されず、ブロードキャストされた追加の呼び出しはコントローラでトリガされます。 もし、$rootscope.$broadcastの問題を解決する方法をご存じでしたら、ご回答をお願いします。

しかし、先ほどの話の繰り返しになりますが、サービスプロパティにバインドする方法のベストプラクティスを知りたいのです。


アップデート

この質問は、2013年4月に行われた質問と回答です。2014年5月にGil Birmanが新しい回答を提供し、私はそれを正しい回答として変更しました。Gil Birmanの回答にはほとんどアップボートがないので、この質問を読む人が彼の回答を無視して、より多くの票を持つ他の回答を優先してしまうことを私は懸念しています。 何がベストの答えか判断する前に、ギル・バーマンさんの答えを強くお勧めします。

どのように解決するのですか?

いくつかのことを考える 第二のアプローチの長所と短所 :

  • 0 {{lastUpdated}} ではなく {{timerData.lastUpdated}} である可能性があります。 {{timer.lastUpdated}} その方が読みやすいと思うのですが(でも、議論はやめましょう...)。この点については中立的な評価をしていますので、ご自身で判断してください)

  • +1 コントローラがマークアップのAPIとして機能し、データモデルの構造が変化しても、コントローラの API マッピング htmlパーシャルに触れることなく。

  • -1 しかし、理論は必ずしも実践的ではなく、私は通常、マークアップを修正する必要があることに気づきました。 コントローラのロジックを変更する必要があります。 いずれにせよ . そのため、APIを記述する余分な労力は、その利点を否定することになります。

  • -1 さらに、この方法はあまりDRYではありません。

  • -1 にデータをバインドしたい場合 ng-model を再パッケージ化しなければならないので、コードはさらにDRYでなくなります。 $scope.scalar_values をコントローラに追加して、新しいRESTを呼び出すことができます。

  • -0.1 余分なウォッチャーを作成すると、パフォーマンスに若干の影響があります。また、特定のコントローラで監視する必要のないデータプロパティがモデルにアタッチされている場合、ディープウォッチャーに追加のオーバーヘッドを発生させます。

  • -1 複数のコントローラで同じデータモデルを必要とする場合はどうすればよいでしょうか。つまり、モデルを変更するたびに更新するAPIが複数あるということです。

$scope.timerData = Timer.data; というのは、なんだかとても魅力的な響きですね...。 では、最後のポイントについて、もう少し掘り下げてみましょう。モデルの変更とはどのようなものでしょうか?バックエンド(サーバ)上のモデルでしょうか?それとも、フロントエンドで作成され、フロントエンドにのみ存在するモデルですか?どちらの場合でも、本質的に データマッピングAPI に属します。 フロントエンドサービスレイヤー アンギュラーファクトリーまたはサービス)。(あなたの最初の例--私の好みですが--には、そのようなAPIが サービス層 これはシンプルなので、必要ないのですが...。)

結論から言うと すべてを切り離す必要はありません。そして、マークアップをデータモデルから完全に切り離すことに関しては、欠点が利点を上回ります。


コントローラ全般 を散りばめてはいけません。 $scope = injectable.data.scalar 's. むしろ $scope = injectable.data 's, promise.then(..) および $scope.complexClickAction = function() {..} 's

データデカップリング、ひいてはビューのカプセル化を実現するための代替アプローチとして、唯一の場所である モデルからビューを切り離すことは本当に意味があることなのです。 ディレクティブで . しかし、そこでも $watch のスカラー値は controller または link 関数を使用します。これでは、時間の節約にもなりませんし、コードの保守性や可読性が向上するわけでもありません。テストが簡単になるわけでもありません。 angularの堅牢なテストは、通常、結果のDOMをテストします。 . むしろ、ディレクティブの要求で、あなたの データAPI をオブジェクト形式で使用することを推奨します。 $watch によって作成された ng-bind .


http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio

<body ng-app="ServiceNotification">
    <div style="border-style:dotted" ng-controller="TimerCtrl1">
        TimerCtrl1<br/>
        Bad:<br/>
        Last Updated: {{lastUpdated}}<br/>
        Last Updated: {{calls}}<br/>
        Good:<br/>
        Last Updated: {{data.lastUpdated}}<br/>
        Last Updated: {{data.calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.data = Timer.data;
            $scope.lastUpdated = Timer.data.lastUpdated;
            $scope.calls = Timer.data.calls;
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 500);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>


アップデイト : 私はどちらのアプローチも間違ってはいないと思いますので、最後にこの質問に戻ってきました。当初、私はJosh David Millerの回答が正しくないと書いていましたが、振り返ってみると、彼の指摘は完全に妥当であり、特に懸念の分離に関する指摘は重要です。

懸念事項の分離はさておき(ただし、余談ながら)、もうひとつの理由は ディフェンシブコピー を検討しそびれていました。この質問は、ほとんどサービスから直接データを読み取ることを扱っています。しかし、あなたのチームの開発者が、ビューがデータを表示する前に、コントローラが何らかの些細な方法でデータを変換する必要があると判断したらどうでしょうか。(コントローラがデータを変換する必要があるかどうかは、また別の議論になります。) もし最初にオブジェクトのコピーを作成しなければ、同じデータを消費する別のビューコンポーネントで、知らず知らずのうちにリグレッションを引き起こしてしまうかもしれません。

この質問は、典型的なangularアプリケーション(そして本当にあらゆるJavaScriptアプリケーション)のアーキテクチャ上の欠点である、懸念事項の密結合とオブジェクトの変異可能性を強調しています。私は最近、Reactを使ったアプリケーションのアーキテクチャに夢中になっています。 のデータ構造です。そうすることで、以下の2つの問題を見事に解決することができます。

  1. 懸念事項の分離 : コンポーネントはpropsを介してすべてのデータを消費し、グローバルシングルトン(Angularサービスなど)にはほとんど依存せず、何が起こったかについては何も知らない。 上記 ビューの階層にある

  2. ミュータビリティ : 全てのプロップはイミュータブル(不変)であるため、知らず知らずのうちにデータが変化するリスクを排除します。

Angular 2.0は、上記の2点を実現するために、Reactから大きく借用する方向で進んでいます。