将服务数据绑定到角度视图的最佳实践是什么(1.3)

时间:2021-09-03 20:07:53

I'm new to Angular, but have a fair amount of experience in coding and am having some trouble getting data binding in views "right".

我刚接触过角度,但在编码方面有相当丰富的经验,而且在获取视图“正确”的数据绑定时遇到了一些麻烦。

My approach is broadly:

我的方法是广泛:

  • Keep all logic out of controllers and encapsulate them in services (see this blog)
  • 将所有的逻辑从控制器中删除,并将它们封装到服务中(请参阅本博客)
  • Use the ControllerAs syntax as per John Papa's style guide.
  • 按照John Papa的风格指南使用ControllerAs语法。

The confusion I'm having is that not having an explicit $scope object obfuscates the model in the MVC philosophy. There was a passing comment to this effect in the blog post, but I didn't see any real conclusion on the matter.

我所困惑的是,没有一个明确的$scope对象会在MVC哲学中混淆模型。在这篇博文中,我对这一现象有过一些简短的评论,但我并没有看到任何关于这一问题的真正结论。

The Angular Docs state that the model is "the single point of truth" for data. Perhaps I read to much into this (is it just the "truth" of values between the V and C?) but this suggests that, if you're using ControllerAs syntax, that the controller properties are the model -- and the "single point of truth".

角文档声明模型是数据的“单点真实”。也许我读了很多(这只是V和C之间值的“真相”吗?)但这表明,如果使用ControllerAs语法,控制器属性就是模型——以及“唯一真相”。

If that's a given, and you want to abstract all logic into services, then as I see it, you can't get away from one of two pretty ugly alternatives for keeping the controller properties in sync with the service values:

如果这是给定的,你想要将所有的逻辑抽象到服务中,那么在我看来,你无法摆脱两个非常丑陋的选项之一,即保持控制器属性与服务值同步:

  1. Expose the service to the view. i.e. myController.data = service.data. This works, but is pretty gross, because all the service data is exposed to the view, and isn't the point of these thin controllers to act as a facade to the view? Or to look at it another way, the service has now become a de facto 'fat' controller, full of business logic.
  2. 将服务公开给视图。即myController。data = service.data。这是可行的,但相当粗略,因为所有的服务数据都公开给视图,这些瘦控制器的目的不就是充当视图的facade吗?或者换个角度来看,这个服务现在已经变成了一个事实上的“胖”控制器,充满了业务逻辑。
  3. Expose only the elements from the service that you want the view to access, but put $watch expressions on each of them. This is not ideal because
    • You add a lot of boilerplate to your controllers
    • 你给控制器添加了很多样板文件
    • You add significantly to the number of watch expressions in your code (angular puts a watch on the template <-> controller link, and you add the controller <-> service link; for each variable in )
    • 您可以显著地增加代码中表表达式的数量(角度在模板<->控制器链接上放置一个表,然后添加控制器<->服务链接);对于每个变量in)
  4. 只公开希望视图访问的服务中的元素,但将$watch表达式放在每个元素上。这并不理想,因为你在控制器中添加了很多样板文件你显著地增加了代码中手表表达式的数量(角将手表放在模板<->控制器链接上,添加控制器<->服务链接;对于每个变量in)

Are these really the only ways of doing this in Angular 1.3? Or is there something (more elegant) that I've missed?

这是角1。3中唯一的方法吗?还是我错过了什么(更优雅)?

Edit: Based on the answers and some additional investigations, I've collected my thoughts on the question in this plunkr. It covers 3 ways (IMHO) not to do service binding and two ways that are probably closer to the 'right' approach.

编辑:根据答案和一些额外的调查,我收集了我对这个问题的看法。它包括三种不进行服务绑定的方法(IMHO)和两种可能更接近“正确”方法的方法。

1 个解决方案

#1


2  

Services return models. Controllers interact with services to populate the view model ($scope). The view interacts with the view model to render the view.

服务恢复模式。控制器与服务交互以填充视图模型($scope)。视图与视图模型交互,以呈现视图。

To keep data in-sync, you have a few options available.

为了保持数据同步,您有一些可用的选项。

Option 1: Introduce a factory or service whose job is to track instances - like a caching service. Any controller that needs an instance for binding to the view can ask for it from the service. This is clean - no need for explicit watch expressions, or non-specific model binding.

选项1:引入一个工厂或服务,其工作是跟踪实例——如缓存服务。任何需要绑定到视图的实例的控制器都可以从服务请求它。这是干净的-不需要显式表表达式,或非特定的模型绑定。

Here is an example. Suppose you want to bind to a User object across multiple controllers.

这是一个例子。假设您想跨多个控制器绑定到一个用户对象。

Create a User Service

创建一个用户服务

app.factory('UserService', function() {
    return {
        getUserById: function(id) {
           ...
           return user;
        }
    }
});

Create a Cached User Service

创建缓存的用户服务。

app.factory('CachedUserService', function(UserService) {
     var cache = [];
     return {
         getUserById: function(id) {
             if (!cache[id])
                 cache[id] = UserService.getUserById(id);                    
             return cache[id];                 
         }
     }
});

Here, we are leveraging singletons (which all factories and services are), and relying on the Caching service to preserve the model reference. The latter point is essential for making sure that view changes are in sync with model changes.

在这里,我们利用单例(所有工厂和服务都是单例),并依赖缓存服务来保存模型引用。后一点对于确保视图更改与模型更改同步至关重要。

Use the Cached User Service in any controller that requires it:

在任何需要它的控制器中使用缓存的用户服务:

app.controller('ctrl', function($scope, CachedUserService) {
     $scope.user = CachedUserService.getUserById(...);
});

Option 2: Simply store the model that you would like to keep in sync, higher up the $scope chain (i.e. $rootScope)

选项2:简单地存储您希望保持同步的模型,在$scope链的更高一级(例如$rootScope)

app.controller('ctrl', function($scope, $rootScope, UserService) {
      $rootScope.user = UserService.getUserById(...);
} 

Any child scopes are able to bind to the model higher up the scope chain because of prototypical scope inheritance. Take care, however, when you assign or overwrite models on $scope - you may inadvertently break the model binding. To preserve the reference, use angular.copy instead.

由于原型范围继承,任何子作用域都能够绑定到作用域链更高的模型。但是,当您在$scope上分配或覆盖模型时要小心——您可能无意中破坏了模型绑定。为了保持参考,使用角度。而不是副本。

#1


2  

Services return models. Controllers interact with services to populate the view model ($scope). The view interacts with the view model to render the view.

服务恢复模式。控制器与服务交互以填充视图模型($scope)。视图与视图模型交互,以呈现视图。

To keep data in-sync, you have a few options available.

为了保持数据同步,您有一些可用的选项。

Option 1: Introduce a factory or service whose job is to track instances - like a caching service. Any controller that needs an instance for binding to the view can ask for it from the service. This is clean - no need for explicit watch expressions, or non-specific model binding.

选项1:引入一个工厂或服务,其工作是跟踪实例——如缓存服务。任何需要绑定到视图的实例的控制器都可以从服务请求它。这是干净的-不需要显式表表达式,或非特定的模型绑定。

Here is an example. Suppose you want to bind to a User object across multiple controllers.

这是一个例子。假设您想跨多个控制器绑定到一个用户对象。

Create a User Service

创建一个用户服务

app.factory('UserService', function() {
    return {
        getUserById: function(id) {
           ...
           return user;
        }
    }
});

Create a Cached User Service

创建缓存的用户服务。

app.factory('CachedUserService', function(UserService) {
     var cache = [];
     return {
         getUserById: function(id) {
             if (!cache[id])
                 cache[id] = UserService.getUserById(id);                    
             return cache[id];                 
         }
     }
});

Here, we are leveraging singletons (which all factories and services are), and relying on the Caching service to preserve the model reference. The latter point is essential for making sure that view changes are in sync with model changes.

在这里,我们利用单例(所有工厂和服务都是单例),并依赖缓存服务来保存模型引用。后一点对于确保视图更改与模型更改同步至关重要。

Use the Cached User Service in any controller that requires it:

在任何需要它的控制器中使用缓存的用户服务:

app.controller('ctrl', function($scope, CachedUserService) {
     $scope.user = CachedUserService.getUserById(...);
});

Option 2: Simply store the model that you would like to keep in sync, higher up the $scope chain (i.e. $rootScope)

选项2:简单地存储您希望保持同步的模型,在$scope链的更高一级(例如$rootScope)

app.controller('ctrl', function($scope, $rootScope, UserService) {
      $rootScope.user = UserService.getUserById(...);
} 

Any child scopes are able to bind to the model higher up the scope chain because of prototypical scope inheritance. Take care, however, when you assign or overwrite models on $scope - you may inadvertently break the model binding. To preserve the reference, use angular.copy instead.

由于原型范围继承,任何子作用域都能够绑定到作用域链更高的模型。但是,当您在$scope上分配或覆盖模型时要小心——您可能无意中破坏了模型绑定。为了保持参考,使用角度。而不是副本。