【问题标题】:AngularJS DRY controller structureAngularJS DRY 控制器结构
【发布时间】:2014-07-10 12:41:52
【问题描述】:

下面的代码表示在每个处理来自服务器的数据的控制器中重复相同的代码模式的情况。经过长时间的研究和在 #angularjs 的 irc 演讲后,我仍然无法弄清楚如何抽象该代码,内联 cmets 解释了这些情况:

myApp.controller("TodoCtrl", function($scope, Restangular,
                                      CalendarService, $filter){
    var all_todos = [];
    $scope.todos = [];
    Restangular.all("vtodo/").getList().then(function(data){
        all_todos = data;
        $scope.todos = $filter("calendaractive")(all_todos);
    });
    //I can see myself repeating this line in every 
    //controller dealing with data which somehow relates
    //and is possibly filtered by CalendarService:
    $scope.activeData = CalendarService.activeData;
    //also this line, which triggers refiltering when
    //CalendarService is repeating within other controllers
    $scope.$watch("activeData", function(){
        $scope.todos = $filter("calendaractive")(all_todos);
    }, true);

});

//example. another controller, different data, same relation with calendar?
myApp.controller("DiaryCtrl", function($scope, Restangular,
                                       CalendarService, $filter){
    //this all_object and object seems repetitive,
    //isn't there another way to do it? so I can keep it DRY?
    var all_todos = [];
    $scope.todos = [];
    Restangular.all("diary/").getList().then(function(data){
        all_diaries = data;
        $scope.diaries = $filter("calendaractive")(all_diaries);
    });
    $scope.activeData = CalendarService.activeData;
    $scope.$watch("activeData", function(){
        $scope.todos = $filter("calendaractive")(all_diaries);
    }, true);
});

【问题讨论】:

    标签: angularjs controller dry restangular


    【解决方案1】:

    应该有目的地而不是热心地遵循 DRY。您的代码很好,控制器正在做他们应该做的事情:连接应用程序的不同部分。也就是说,您可以轻松地将重复的代码组合到一个返回函数引用的工厂方法中。

    例如,

    myApp.factory('calendarScopeDecorator', function(CalendarService, Restangular, $filter) {
      return function($scope, section) {
        $scope.todos = [];
        $scope.activeData = CalendarService.activeData;
        Restangular.all(section+"/").getList().then(function(data){
          $scope.all_data = data;
          $scope.filtered_data = $filter("calendaractive")(data);
        });
        $scope.$watch("activeData", function(){
          $scope.todos = $filter("calendaractive")($scope.all_data);
        }, true);
      }
    });
    

    然后将其合并到您的控制器中:

    myApp.controller("DiaryCtrl", function($scope, calendarScopeDecorator){
      calendarScopeDecorator($scope, 'diary');
    });
    

    【讨论】:

    • 太好了,我将立即实现这个工厂,因为它的功能将在许多控制器中重复使用。我将使用@Chad 对 $emit/$on 的解释来实施您建议的工厂。 ty
    • 请注意,使用$emit 不再比$broadcast 更有效。这是 Angular 早期版本中的一个问题,但如果您使用的是 Angular 1.2 或 1.3 的最新版本,则该问题已得到解决。
    • 在我的应用程序上下文中,我不得不使用 $broadcast,因为 $emit 没有到达我需要的地方
    【解决方案2】:

    我不会像在这个控制器中那样使用观察者和本地引用来做这种事情。相反,我会使用 $on() / $emit() 来建立从服务到关心其更新的控制器的发布-订阅模式。这是一个未充分使用的模式 IMO,它提供了一个更“干”的机制。它也非常有效——通常比观察者更有效,因为它不需要运行摘要就可以知道发生了什么变化。在基于网络的服务中,您几乎总是可以确定地知道这一点,并且您不需要从确定地知道它到在其他位置暗示它。这可以让您避免 Angular 对对象进行深入检查的成本:

    $rootScope.$on('calendarDiariesUpdated', function() {
        // Update your $scope.todos here.
    }, true);
    

    为您服务:

    // When you have a situation where you know the data has been updated:
    $rootScope.$emit('calendarDiariesUpdated');
    

    请注意,emit/on 比使用广播更有效,广播将通过所有嵌套范围。您也可以通过这种方式将数据从服务传递到监听控制器。

    这是一项非常重要的技术,它可以做一些事情:

    1. 您不再需要对 activeData 进行本地引用,因为您实际上并没有使用它(它是 DRY)。

    2. 在大多数/许多情况下,这比观察者更有效。 Angular 不需要知道你需要被告知更新 - 你知道你这样做。这也是一种 DRY 原则——为什么要使用框架工具来做一些你实际上并不需要的事情?这是一个额外的步骤,将数据放在某个地方,然后等待 Angular 消化它并说“哇,你需要知道这个。”

    3. 您甚至可以减少注射次数。无需使用 CalendarService,因为该服务可以在其通知中直接传递对数组的引用。这很好,因为如果您更改服务中的存储模型,您以后不需要重构它(DRY 倡导者也提倡的事情之一就是抽象这些东西)。

    您确实需要使用 $rootScope 以便注册观察者,但 pub-sub 概念中没有任何违反 DRY 的内容。它非常普遍和被接受(最重要的是:它表现得非常好)。这与原始全局变量不同,这也是人们一开始就不敢使用 $rootScope 的原因(而且通常是这样)。

    如果您想成为“Super DRY”,您可以将对 $filter 的调用重构为执行过滤的单个方法,并从您的 REST 承诺解决方案和日历更新通知中调用它。这实际上添加了几行代码......但没有重复任何内容。 :) 原则上这可能是一个好主意,因为您可能会维护该特定行(它需要静态“日历活动”参数...)

    【讨论】:

    • 谢谢!我真的认为你的话会帮助许多其他 angularjs 开发人员。我正在重构我的代码以使用 on/emmit 而不是观察对象。我真的不喜欢我观察对象的方式,因为我有点失去了返回对象的吸气剂,而不是调用函数。
    • 在以后的 1.2.x 版本中修复了“遍历所有嵌套范围”的 $broadcast 问题。 JSPerf using 1.2.0RC3JSPerf using 1.2.16
    【解决方案3】:

    看起来 2 个控制器中的代码除了 API 调用的路径是相同的:“vtodo/”或“diary/”。

    实现更接近 DRY-ness 的一种方法是将 API 路径作为选项作为属性传递给控制器​​。所以,假设我们调用控制器ApiController,这可以用作

    <div ng-controller="ApiController" api-controller-path="vtodo/">
      <!-- Todo template -->
    </div> 
    

    <div ng-controller="ApiController" api-controller-path="diary/">
      <!-- Diary template -->
    </div> 
    

    然后可以通过注入的$attrs 参数在控制器中访问:

    myApp.controller("ApiController", function($scope, $attrs, Restangular, CalendarService, $filter) {
      // "vtodo/" or "diary/"
      var apiPath = $attrs.apiControllerPath;
    

    作为警告,我会提防过度架构,并非所有内容都需要考虑在内,并且有人认为您只是遵循设计模式而不是复制+粘贴代码。但是,我自己也使用上述方法将选项传递给控制器​​以应对类似情况。

    【讨论】:

    • 它们有很多相似的部分,但没有那么多。它们会运行不同的过滤器,有不同的函数调用,等等。
    【解决方案4】:

    如果您担心对同一资源 CalendarService 进行多次调用,我建议您找到一种方法来缓存结果,例如在该服务中定义一个变量或使用 Angular 的 $cacheFactory

    否则,我看不出您的模式有任何错误

    【讨论】:

    • CalendarService 不存在问题。 CalendarService 是一个单例服务,它将返回活动日历,多次调用此服务不是我的问题的重点。我担心的是控制器上的非 DRY 方法,我必须重新观察日历服务。 activeData,我认为这可以以某种方式抽象..
    • 我明白你的意思。我确实误会了。我的猜测是因为服务会跟踪更新,而$watch 是让控制器知道发生了一些变化的唯一方法,这可能是最好的方法。当通过使用服务在我的应用程序中更改某些上下文时,我实际上使用了类似的设置。我最终让每个控制器都监视这个变量以更新作用域的数据模型。
    • 它有效,但感觉不干燥。我找不到其他方向。感谢您的反馈。
    猜你喜欢
    • 2014-02-11
    • 2014-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-22
    • 1970-01-01
    • 2013-12-09
    相关资源
    最近更新 更多