首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >AngularJS :绑定到服务属性的正确方式

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

Stack Overflow用户
提问于 2013-04-04 08:14:01
回答 10查看 114.8K关注 0票数 162

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

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

下面我有两个如何绑定到服务中的属性的示例;它们都有效。第一个示例使用基本绑定,第二个示例使用$scope.$watch绑定到服务属性

在绑定到服务中的属性时,这两个示例中的任何一个是首选的,还是有其他我不知道的选项值得推荐?

这些示例的前提是服务应该每5秒更新一次属性“lastUpdated”和“calls”。更新服务属性后,视图应反映这些更改。这两个例子都很成功;我想知道是否有更好的方法。

Basic Binding

可以在此处查看和运行以下代码: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。

$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提供了一个新的答案,我将其更改为正确答案。由于吉尔·比尔曼的回答获得的赞成票很少,我担心的是,阅读这个问题的人会忽略他的答案,转而支持其他拥有更多选票的答案。在你决定最好的答案之前,我强烈推荐吉尔·比尔曼的答案。

EN

回答 10

Stack Overflow用户

回答已采纳

发布于 2014-05-21 13:40:29

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

  • {{lastUpdated}}而不是{{timerData.lastUpdated}},它可以很容易地成为{{timer.lastUpdated}},我可能会认为它更具可读性(但我们不要争辩...我给这一点一个中立的评级,所以你决定对于yourself)
  • +1,控制器充当标记的API可能更方便,这样如果数据模型的结构发生了某种变化,你(理论上)可以在不接触html部分的情况下更新控制器的API映射。
  • -1然而,理论并不总是实践,不管怎样,当需要更改时,我经常发现自己不得不修改标记和控制器逻辑。因此,编写API的额外努力否定了它的advantage.
  • -1此外,这种方法不是很DRY.
  • -1如果你想将数据绑定到call.
  • -0.1你的代码会变得更不枯燥,因为你必须在控制器中重新打包ng-model来生成一个新的REST $scope.scalar_values创建额外的监视器会有很小的性能影响。此外,如果数据属性附加到不需要在特定控制器中监视的模型,它们将为深度watchers.
  • -1创建额外的开销,如果多个控制器需要相同的数据模型怎么办?这意味着您需要在每次模型更改时更新多个API。

$scope.timerData = Timer.data;现在听起来很有诱惑力……让我们更深入地研究最后一点...我们谈论的是哪种模式的改变?后端(服务器)上的模型?还是一个只存在于前端的模型?在这两种情况下,数据映射API本质上都属于前端服务层( angular工厂或服务)。(请注意,您的第一个示例--我的偏好--在服务层中没有这样的API,这很好,因为它足够简单,它不需要它。)

在结论中,所有的东西都不需要解耦。就将标记与数据模型完全解耦而言,缺点大于优点。

控制器,一般来说,不应该到处都是$scope = injectable.data.scalar,而应该是$scope = injectable.datapromise.then(..)$scope.complexClickAction = function() {..}

作为实现数据解耦和视图封装的另一种方法,将视图从模型中解耦的唯一有意义的地方是使用指令的。但即便如此,也不要在controllerlink函数中使用$watch标量值。这不会节省时间,也不会使代码更具可维护性和可读性。它甚至不会使测试变得更容易,因为angular中的健壮测试通常会测试生成的DOM。相反,在指令中要求以对象形式提供数据应用程序接口,并且只支持使用由ng-bind创建的$watcher。

示例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和不可变数据结构设计应用程序。这样做很好地解决了以下两个问题:

  1. Separation of concerns:组件通过属性使用所有数据,几乎不依赖全局单例(如角度服务),并且在视图中对它上面发生的事情一无所知。所有属性都是不可变的,这消除了无意中数据突变的风险。

Angular 2.0现在正在向React借用大量资源,以实现上述两点。

票数 99
EN

Stack Overflow用户

发布于 2013-04-04 08:22:31

在我看来,$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语句)中不包含任何“逻辑”时,他们遵循了相同的推理。

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

票数 78
EN

Stack Overflow用户

发布于 2016-04-06 11:37:07

聚会迟到了,但对于未来的谷歌人来说--不要使用提供的答案。

JavaScript有一种通过引用传递对象的机制,而它只传递“数字、字符串等”值的浅层副本。

在上面的示例中,我们为什么不将服务公开给作用域?,而不绑定服务的属性

$scope.hello = HelloService;

这种简单的方法将使angular能够进行双向绑定,并实现您需要的所有神奇功能。不要用监视器或不需要的标记来攻击你的控制器。

如果您担心视图会意外覆盖服务属性,可以使用defineProperty使其可读、可枚举、可配置,或者定义getter和setter。你可以通过让你的服务更稳定来获得更多的控制权。

最后一条提示:如果你花在控制器上的时间超过了你的服务,那么你就错了:(.

在您提供特定演示代码中,我建议您这样做:

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

编辑:

如上所述,您可以使用defineProperty控制服务属性的行为

示例:

// Lets expose a property named "propertyWithSetter" on our service
// and hook a setter function that automatically saves new value to db !
Object.defineProperty(self, 'propertyWithSetter', {
  get: function() { return self.data.variable; },
  set: function(newValue) { 
         self.data.variable = newValue; 
         // let's 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的值,并以某种方式将新值发布到数据库!

或者我们可以采取任何我们想要的方法。

请参阅definePropertyMDN documentation

票数 21
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15800454

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档