AngularJS:绑定到服务属性的正确方法

我正在寻找如何绑定到AngularJS中的服务属性的最佳做法。

我已经通过多个示例来了解如何绑定到使用AngularJS创建的服务中的属性。

下面我有两个如何绑定到服务中的属性的例子; 他们都工作。 第一个示例使用基本绑定,第二个示例使用$ 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。

$范围。$腕表

下面的代码可以在这里查看并运行: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 / $的示例中,控制器,但广播的其他呼叫在控制器中触发。 如果您知道解决$ rootscope。$ broadcast问题的方法,请提供答案。

但为了重申我之前提到的内容,我想知道如何绑定到服务属性的最佳做法。


更新

这个问题最初是在2013年4月提出并回答的。2014年5月,Gil Birman提供了一个新答案,我将其作为正确答案进行了更改。 由于吉尔伯曼的回答很少有人赞成,我担心的是,阅读这个问题的人会不理会他的回答,赞成其他答案,并得到更多的选票。 在你决定什么是最好的答案之前,我强烈推荐Gil Birman的答案。


考虑第二种方法的一些利弊

  • 0 {{lastUpdated}}而不是{{timerData.lastUpdated}} ,它可能很容易成为{{timer.lastUpdated}} ,我可能会争辩{{timer.lastUpdated}}更具可读性(但我们不要争辩......我给这个指出一个中立的评级,所以你自己决定)

  • +1控制器可以很方便地用作标记的一种API,这样如果数据模型的结构发生变化,您可以(理论上)更新控制器的API映射而不需要触及html部分。

  • -1然而,理论并不总是实践,无论如何,当我们要求修改标记和控制器逻辑时,我通常会发现自己不得不修改标记和控制器逻辑。 所以编写API的额外努力否定了它的优势。

  • -1此外,这种方法不是非常干燥。

  • -1如果你想把数据绑定到ng-model你的代码变得更加干燥,因为你必须重新打包控制器中的$scope.scalar_values来创建一个新的REST调用。

  • -0.1创建额外观察者的表现很小。 另外,如果数据属性附加到不需要在特定控制器中观看的模型,则会为深层观察者创建额外开销。

  • -1如果多个控制器需要相同的数据模型会怎样? 这意味着您可以使用多个API更新每个模型更改。

  • $scope.timerData = Timer.data; 现在开始听起来很有吸引力......让我们深入探讨最后一点......我们在谈论什么样的模型变化? 后端(服务器)上的模型? 或者只是在前端创建并生活的模型? 在任何一种情况下,数据映射API本质上都属于前端服务层(角色工厂或服务)。 (请注意,您的第一个示例 - 我的偏好 - 在服务层中没有这样的API,这很好,因为它足够简单并且不需要它。)

    总之 ,一切都不必解耦。 就标记完全脱离数据模型而言,缺点大于优点。


    一般来说,控制器不应该包含$scope = injectable.data.scalar的。 相反,他们应该洒$scope = injectable.data $scope.complexClickAction = function() {..}的, $scope.complexClickAction = function() {..} promise.then(..)的和$scope.complexClickAction = function() {..}

    作为实现数据解耦和视图封装的另一种方法, 将视图与模型分离的唯一方法是使用指令 。 但即使在那里,也不要$watchcontrollerlink函数中$watch标量值。 这不会节省时间或使代码更易于维护和读取。 它甚至不会使测试更容易,因为角度的强健测试通常会测试生成的DOM。 相反,在一个指令中要求对象形式的数据API,并且只使用由ng-bind创建的$watch watchrs。


    示例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>
    

    更新 :我终于回到这个问题补充说,我不认为这两种方法都是“错误的”。 本来我写过乔希大卫米勒的回答是不正确的,但回想起来,他的观点是完全有效的,尤其是他关于分离关注点的观点。

    抛开关注点(但与切线相关),我没有考虑防御性复制的另一个原因。 这个问题主要涉及直接从服务中读取数据。 但是如果你的团队中的开发人员决定在视图显示之前控制器需要以一些微不足道的方式转换数据呢? (控制器是否应该转换数据是另一个讨论。)如果她不先制作对象的副本,她可能会不知不觉地导致消耗相同数据的另一个视图组件中的回归。

    这个问题真正强调的是典型的角度应用程序(以及任何JavaScript应用程序)的架构缺陷:紧密耦合关注点和对象可变性。 我最近迷恋于用React和不可变的数据结构构建应用程序。 这样做可以很好地解决以下两个问题:

  • 问题分离 :组件通过道具消耗其所有数据,并且几乎不依赖全局单例(比如Angular服务),并且对视图层次结构中发生的事情一无所知。

  • 可变性 :所有道具都是不可变的,消除了不知情的数据变异的风险。

  • Angular 2.0现在有望从React大量借用以实现上述两点。


    从我的角度来看, $watch将是最佳实践方式。

    你实际上可以简化你的例子:

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

    这就是你需要的。

    由于属性同时更新,因此您只需要一只手表。 而且,由于它们来自单个而非小型的对象,因此我将其更改为仅查看Timer.data属性。 传递给$watch的最后一个参数告诉它检查深度平等,而不是仅仅确保参考是相同的。


    为了提供一个小背景,我宁愿这种方法将服务价值直接放在范围上的原因是为了确保正确分离关注点。 您的观点不需要知道任何关于您的服务以便操作。 控制器的工作是将所有东西粘合在一起; 它的工作就是从你的服务中获取数据并以任何必要的方式处理它们,然后为你的视图提供所需的任何细节。 但我不认为它的工作就是将服务正确地传递给观点。 否则,控制器甚至在那里做什么? 当AngularJS开发者选择不在模板中包含任何“逻辑”时(例如if语句),他们遵循相同的推理。

    公平地说,这里可能有多种观点,我期待着其他答案。


    晚会派对,但未来的谷歌搜索不要使用提供的答案。

    js具有通过引用传递对象的独特机制,而它只传递值为“数字,字符串等”的浅拷贝。

    在上面的例子中,而不是绑定服务的属性。 为什么我们不把服务暴露给范围?

    $scope.hello = HelloService;
    

    这种简单的方法将使角度能够做2路绑定和所有你需要的神奇东西。 不要用监视器或不完整的标记来破解你的控制器。

    如果您担心自己的观点意外地覆盖了您的服务属性。 精简使用DefineAttribute()使其具有可读性,可枚举性,可配置性,设置getter和setter。 你给它命名。 通过使您的服务更加稳固,您可以获得很多控制权。

    最后提示:如果您花时间在控制器上工作,那么您的服务更多。 那么你做错了:(。

    在你提供的特定演示代码中,我会建议你这样做

     function TimerCtrl1($scope, Timer) {
      $scope.timer = Timer;
     }
    ///Inside view
    {{ timer.time_updated }}
    {{ timer.other_property }}
    etc...
    

    3岁以上的帖子太多,但如果有人需要更多的信息让我知道

    编辑:

    正如我上面提到的,您可以使用defineProperty控制服务属性的行为

    例:

    // Lets expose a property named "propertyWithSetter" on our service
    // and hook a setter function that automaticly save new value to db !
    Object.defineProperty(self, 'propertyWithSetter', {
      get: function() { return self.data.variable; },
      set: function(newValue) { 
             self.data.variable = newValue; 
             //lets update the database too to reflect changes in data-model !
             self.updateDatabaseWithNewData(data);
           },
      enumerable: true,
      configurable: true
    });
    

    如果我们这样做,现在在控制器中

    $scope.hello = HelloService;
    $scope.hello.propertyWithSetter = 'NEW VALUE';
    

    我们的服务将改变propertyWithSetter的价值,并以某种方式向数据库发布新的价值!

    或者我们可以采取我们想要的任何方法。 请参考https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

    感谢upvotes :)

    链接地址: http://www.djcxy.com/p/77557.html

    上一篇: AngularJS : The correct way of binding to a service properties

    下一篇: Injecting a mock into an AngularJS service