AngularJS: サービスプロパティにバインドする正しい方法 質問する

AngularJS: サービスプロパティにバインドする正しい方法 質問する

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

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

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

これらの例のいずれかが、サービス内のプロパティにバインドするときに推奨されますか、それとも私が知らない推奨される別のオプションがありますか?

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

基本的なバインディング

以下のコードはここで表示および実行できます: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>

サービス プロパティへのバインディングを解決するもう 1 つの方法は、コントローラーで $scope.$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 の回答にはほとんど賛成票がないため、この質問を読んだ人が彼の回答を無視して、より多くの票を獲得した他の回答を優先するのではないかと心配しています。最適な回答を決定する前に、Gil Birman の回答を強くお勧めします。

ベストアンサー1

いくつか考えてみましょう2番目のアプローチの長所と短所:

  • 0 {{lastUpdated}}の代わりに になります{{timerData.lastUpdated}}。これは になる可能性もあり{{timer.lastUpdated}}、私は の方が読みやすいと主張するかもしれません (しかし、議論はやめましょう...この点については中立的な評価を与えているので、自分で判断してください)

  • +1コントローラーがマークアップのAPIのようなものとして動作し、データモデルの構造が何らかの理由で変更された場合に(理論上は)コントローラーのAPIマッピングHTML 部分に触れることなく。

  • -1しかし、理論は必ずしも実践とは限らず、私はたいていマークアップを修正しなければならないことに気づきますそして変更が必要になったときのコントローラロジックともかくそのため、API を書くための余分な労力がその利点を打ち消します。

  • -1さらに、このアプローチはあまり DRY ではありません。

  • -1データをng-modelコードにバインドする場合は$scope.scalar_values、新しい REST 呼び出しを行うためにコントローラーで再パッケージ化する必要があるため、DRY はさらに低下します。

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

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

$scope.timerData = Timer.data;そろそろ魅力的に聞こえ始めているようです...最後のポイントについてもう少し深く掘り下げてみましょう...どのようなモデルの変更について話していたのでしょうか?バックエンド(サーバー)のモデルですか?それともフロントエンドでのみ作成され、存在するモデルですか?どちらの場合でも、データマッピングAPIに属するフロントエンドサービス層、(角度付きファクトリーまたはサービス)。(最初の例(私の好み)には、そのようなAPIが含まれていません。サービス層、これは必要ないほど単純なので問題ありません。

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


コントローラー全般'sで散らかすべきではありません。むしろ、 's、's、's$scope = injectable.data.scalarで散りばめるべきなのです$scope = injectable.datapromise.then(..)$scope.complexClickAction = function() {..}

データの分離とビューのカプセル化を実現する代替アプローチとして、ビューとモデルを切り離すのは本当に理にかなっています指令付きしかし、それでも、関数や関数$watchにスカラー値を使わないでください。時間の節約にもなりませんし、コードの保守性や可読性も向上しません。テストも簡単になりません。controllerlinkAngularの堅牢なテストでは、通常、結果のDOMをテストします。むしろ、指令では、データAPIオブジェクト形式で、$watchによって作成された ersのみを使用することを推奨します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 から多くの要素を借用する予定です。

おすすめ記事