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

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

问题描述

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



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



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



当绑定到服务中的属性时,这些示例中是优选的还是有另一个选项,我不知道会被推荐吗?



这些例子的前提是服务应该更新其属性lastUpdated和呼叫每5秒钟。一旦服务属性更新,视图应该反映这些更改。这两个例子都是成功的;我想知道是否有更好的方法。



基本绑定



以下代码可以在这里查看并运行: http://plnkr.co/edit/d3c16z

 < html> 
< body ng-app =ServiceNotification>

< divng-controller =TimerCtrl1style =border-style:dotted>
TimerCtrl1< br />
最后更新:{{timerData.lastUpdated}}< br />
最后更新:{{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,[]);

函数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:dottedng-controller =TimerCtrl1>
TimerCtrl1< br />
最后更新:{{lastUpdated}}< br />
最后更新:{{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,[]);

函数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。$在控制器中,但是在我创建的其他示例中,在第一次广播时使用$ broadcast / $不是由控制器捕获的,但是在控制器中触发了广播的其他调用。如果你知道一个方法来解决$ rootscope $广播问题,请提供一个答案。



但是要重申我之前提到的,我想知道





更新



这个问题最初于2013年4月提出并回答。2014年5月,吉尔·比曼提供了一个新的答案,我改为正确答案。由于吉尔·比尔曼(Gil Birman)的回答很少,所以我担心的是,读这个问题的人会忽视他的回答,赞成其他答复多一点。在决定最佳答案之前,我强烈建议Gil Birman的答案。

解决方案

考虑一些




  • 0 { {lastUpdated}} 而不是 {{timerData.lastUpdated}} ,这可以很容易地 {{timer。最后更新}} ,我可能认为这是更可读的(但我们不会争论...我给这个点一个中立的评价,所以你为自己决定)


  • +1 控制器可以方便地用作标记的API,以便如果不知何故,数据模型的结构可以改变(理论上讲) )更新控制器的API映射,而不触及html部分。


  • -1 然而,理论并不总是练习,我通常会发现自己必须修改标记控制器逻辑,当调用更改,无论如何。所以编写API的额外努力取决于它的优势。


  • -1 此外,这种方法不是很干。 / p>


  • -1 如果要将数据绑定到 ng-model 您的代码变得更少,因为您必须在控制器中重新打包 $ scope.scalar_values 以进行新的REST调用。


  • -0.1 创建额外的观察者有一个很小的性能。此外,如果数据属性附加到模型中,不需要在特定控制器中监视,则它们将为深度观察者创建额外的开销。


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




$ scope.timerData = Timer.data; 开始听起来很强大的诱惑吧,现在我们来深入一下,最后一点...我们在谈论什么样的模式变化?后端模型(服务器)?还是一个创造和生活只在前端的模型?在任何一种情况下,本质上数据映射API 属于前端服务层(有角度的工厂或服务)。 (请注意,您的第一个示例 - 我的偏好 - 在服务层中没有这样的API,这很好,因为它很简单,不需要它。)



总而言之,所有事情都不一定要解耦。而且只要将标记完全从数据模型中解离出来,这些缺点就超过了优势。






控制器,一般来说,不应该放在 $ scope = injectable.data.scalar 中。相反,他们应该撒上 $ scope = injectable.data 's, promise.then(..) s和 $ scope.complexClickAction = function(){..}



作为替代方法实现数据解耦,从而实现视图封装,唯一可以通过指令与模型分离视图的唯一方法就是。但是即使在这里,也不要在控制器链接 $ code>函数。这不会节省时间或使代码更可维护和可读性。它甚至不会使测试更容易,因为角度的强大测试通常会测试生成的DOM无论如何。相反,在对象形式的指令要求您的数据API 中,并且仅使用创建的 $ watch -bind






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

  body ng-app =ServiceNotification> 
< div style =border-style:dottedng-controller =TimerCtrl1>
TimerCtrl1< br />
不好:< br />
最后更新:{{lastUpdated}}< br />
最后更新:{{calls}}< br />
好​​:< br />
最后更新:{{data.lastUpdated}}< br />
最后更新:{{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,[]);

函数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的答案是错误的,但回想起来,他的观点是完全有效的,特别是他关于分离问题的观点。



切向相关),还有一个我没有考虑的防御性复制的原因。这个问题主要涉及从服务直接读取数据。但是,如果您的团队中的开发人员决定控制器需要在视图显示之前以一些简单的方式转换数据,该怎么办? (控制器是否完全转换数据是另一个讨论。)如果她没有首先创建对象的副本,可能会在另一个视图组件中不经意地导致消耗相同数据的回归。



这个问题真正突出的是典型角度应用程序(和任何JavaScript应用程序)的架构缺陷:紧密耦合的关注点和对象可变性。我最近喜欢使用React 不可变数据结构的架构应用程序。这样做有助于解决以下两个问题:


  1. 分离问题:组件消耗所有它是通过道具的数据,并且对全球单身人士(例如角色服务)几乎不依赖,并且对视图层次结构中之前发生的事情一无所知。


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


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天全站免登陆