AngularJS:绑定到服务属性的正确方法 [英] AngularJS : The correct way of binding to a service properties

查看:28
本文介绍了AngularJS:绑定到服务属性的正确方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

下面我有两个关于如何绑定到服务中的属性的例子;他们都工作.第一个示例使用基本绑定,第二个示例使用 $scope.$watch 绑定到服务属性

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

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

基本绑定

可以在此处查看和运行以下代码:http://plnkr.co/edit/d3c16z

<body ng-app="ServiceNotification" ><div ng-controller="TimerCtrl1" style="border-style:dotted">TimerCtrl1 <br/>上次更新:{{timerData.lastUpdated}}<br/>最后更新:{{timerData.calls}}<br/>

<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 = 函数 () {data.lastUpdated = new Date();数据调用 += 1;console.log("updateTimer:" + data.lastUpdated);$timeout(updateTimer, 5000);};更新定时器();返回 {数据:数据};});</html>

我解决绑定到服务属性的另一种方法是在控制器中使用 $scope.$watch.

$scope.$watch

可以在此处查看和运行以下代码:http://plnkr.co/edit/dSBlC9

<body ng-app="ServiceNotification"><div style="border-style:dotted" ng-controller="TimerCtrl1">TimerCtrl1<br/>上次更新:{{lastUpdated}}<br/>最后更新:{{calls}}<br/>

<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; },功能(值){console.log("In $watch - lastUpdated:" + value);$scope.lastUpdated = 值;});$scope.$watch(function () { return Timer.data.calls; },功能(值){console.log("在 $watch 中 - 调用:" + value);$scope.calls = 值;});};app.factory("Timer", function ($timeout) {var data = { lastUpdated: new Date(), calls: 0 };var updateTimer = 函数 () {data.lastUpdated = new Date();数据调用 += 1;console.log("updateTimer:" + data.lastUpdated);$timeout(updateTimer, 5000);};更新定时器();返回 {数据:数据};});</html>

我知道我可以在服务中使用 $rootscope.$broadcast 并在控制器中使用 $root.$on,但在我创建的其他示例中,在第一次广播中使用 $broadcast/$ 不是由控制器捕获,但在控制器中触发广播的其他调用.如果您知道解决 $rootscope.$broadcast 问题的方法,请提供答案.

但重申我之前提到的内容,我想知道如何绑定到服务属性的最佳实践.

<小时>更新

这个问题最初是在 2013 年 4 月提出并回答的.2014 年 5 月,Gil Birman 提供了一个新答案,我将其更改为正确答案.由于吉尔伯曼的回答很少有人支持,我担心的是,阅读这个问题的人会无视他的回答,转而支持其他投票更多的答案.在您决定最佳答案之前,我强烈推荐 Gil Birman 的答案.

解决方案

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

  • 0 {{lastUpdated}} 而不是 {{timerData.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 的、promise.then(..) 的和 $scope.complexClickAction = function() {..}

作为实现数据解耦和视图封装的另一种方法,将视图与模型解耦的唯一地方是使用指令.但即便如此,也不要在 controllerlink 函数中 $watch 标量值.这不会节省时间或使代码更具可维护性或可读性.它甚至不会使测试更容易,因为角度中的健壮测试通常无论如何都会测试结果 DOM.相反,在指令中要求您的数据 API 以对象形式存在,并倾向于仅使用由 ng-bind 创建的 $watchers.<小时>

示例http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio

<div style="border-style:dotted" ng-controller="TimerCtrl1">TimerCtrl1<br/>坏:<br/>上次更新:{{lastUpdated}}<br/>最后更新:{{calls}}<br/>好:<br/>上次更新:{{data.lastUpdated}}<br/>最后更新:{{data.calls}}<br/>

<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 = 定时器.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 = 函数 () {data.lastUpdated = new Date();数据调用 += 1;console.log("updateTimer:" + data.lastUpdated);$timeout(updateTimer, 500);};更新定时器();返回 {数据:数据};});

<小时>

更新:我终于回到这个问题来补充说,我认为这两种方法都没有错误".最初我写道 Josh David Miller 的回答是不正确的,但回想起来,他的观点是完全正确的,尤其是他关于关注点分离的观点.

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

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

  1. 关注点分离:组件通过 props 消耗它的所有数据,几乎不依赖全局单例(例如 Angular 服务),并且对发生的事情一无所知在视图层次结构中的上方.

  2. 可变性:所有 props 都是不可变的,从而消除了不知情的数据突变风险.

Angular 2.0 现在有望大量借鉴 React 以实现上述两点.

I’m looking for the best practice of how to bind to a service property in AngularJS.

I have worked through multiple examples to understand how to bind to properties in a service that is created using AngularJS.

Below I have two examples of how to bind to properties in a service; they both work. The first example uses basic bindings and the second example used $scope.$watch to bind to the service properties

Are either of these example preferred when binding to properties in a service or is there another option that I’m not aware of that would be recommended?

The premise of these examples is that the service should updated its properties "lastUpdated" and "calls" every 5 seconds. Once the service properties are updated the view should reflect these changes. Both these example work successfully; I wonder if there is a better way of doing it.

Basic Binding

The following code can be view and ran here: 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>

The other way I solved binding to service properties is to use $scope.$watch in the controller.

$scope.$watch

The following code can be view and ran here: 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>

I’m aware that I can use $rootscope.$broadcast in the service and $root.$on in the controller, but in other examples that I’ve created that use $broadcast/$on the first broadcast is not captured by the controller, but additional calls that are broadcasted are triggered in the controller. If you are aware of a way to solve $rootscope.$broadcast problem, please provide an answer.

But to restate what I mentioned earlier, I would like to know the best practice of how to bind to a service properties.


Update

This question was originally asked and answered in April 2013. In May 2014, Gil Birman provided a new answer, which I changed as the correct answer. Since Gil Birman answer has very few up-votes, my concern is that people reading this question will disregard his answer in favor of other answers with many more votes. Before you make a decision on what's the best answer, I highly recommend Gil Birman's answer.

解决方案

Consider some pros and cons of the second approach:

  • 0 {{lastUpdated}} instead of {{timerData.lastUpdated}}, which could just as easily be {{timer.lastUpdated}}, which I might argue is more readable (but let's not argue... I'm giving this point a neutral rating so you decide for yourself)

  • +1 It may be convenient that the controller acts as a sort of API for the markup such that if somehow the structure of the data model changes you can (in theory) update the controller's API mappings without touching the html partial.

  • -1 However, theory isn't always practice and I usually find myself having to modify markup and controller logic when changes are called for, anyway. So the extra effort of writing the API negates it's advantage.

  • -1 Furthermore, this approach isn't very DRY.

  • -1 If you want to bind the data to ng-model your code become even less DRY as you have to re-package the $scope.scalar_values in the controller to make a new REST call.

  • -0.1 There's a tiny performance hit creating extra watcher(s). Also, if data properties are attached to the model that don't need to be watched in a particular controller they will create additional overhead for the deep watchers.

  • -1 What if multiple controllers need the same data models? That means that you have multiple API's to update with every model change.

$scope.timerData = Timer.data; is starting to sound mighty tempting right about now... Let's dive a little deeper into that last point... What kind of model changes were we talking about? A model on the back-end (server)? Or a model which is created and lives only in the front-end? In either case, what is essentially the data mapping API belongs in the front-end service layer, (an angular factory or service). (Note that your first example--my preference-- doesn't have such an API in the service layer, which is fine because it's simple enough it doesn't need it.)

In conclusion, everything does not have to be decoupled. And as far as decoupling the markup entirely from the data model, the drawbacks outweigh the advantages.


Controllers, in general shouldn't be littered with $scope = injectable.data.scalar's. Rather, they should be sprinkled with $scope = injectable.data's, promise.then(..)'s, and $scope.complexClickAction = function() {..}'s

As an alternative approach to achieve data-decoupling and thus view-encapsulation, the only place that it really makes sense to decouple the view from the model is with a directive. But even there, don't $watch scalar values in the controller or link functions. That won't save time or make the code any more maintainable nor readable. It won't even make testing easier since robust tests in angular usually test the resulting DOM anyway. Rather, in a directive demand your data API in object form, and favor using just the $watchers created by ng-bind.


Example 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>


UPDATE: I've finally come back to this question to add that I don't think that either approach is "wrong". Originally I had written that Josh David Miller's answer was incorrect, but in retrospect his points are completely valid, especially his point about separation of concerns.

Separation of concerns aside (but tangentially related), there's another reason for defensive copying that I failed to consider. This question mostly deals with reading data directly from a service. But what if a developer on your team decides that the controller needs to transform the data in some trivial way before the view displays it? (Whether controllers should transform data at all is another discussion.) If she doesn't make a copy of the object first she might unwittingly cause regressions in another view component which consumes the same data.

What this question really highlights are architectural shortcomings of the typical angular application (and really any JavaScript application): tight coupling of concerns, and object mutability. I have recently become enamored with architecting application with React and immutable data structures. Doing so solves the following two problems wonderfully:

  1. Separation of concerns: A component consumes all of it's data via props and has little-to-no reliance on global singletons (such as Angular services), and knows nothing about what happened above it in the view hierarchy.

  2. Mutability: All props are immutable which eliminates the risk of unwitting data mutation.

Angular 2.0 is now on track to borrow heavily from React to achieve the two points above.

这篇关于AngularJS:绑定到服务属性的正确方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆