AngularJS控制器,DRY代码的设计模式

Ata*_*ais 19 javascript design-patterns dry angularjs angularjs-controller

我已经创建了一个完整的例子来描述这个问题.我的实际应用程序甚至比演示的演示版还要大,每个控制器都有更多的服务和指令.这导致更多的代码重复.我尝试将一些代码注释用于澄清, PLUNKER:http://plnkr.co/edit/781Phn?p = preview

重复部分:

routerApp.controller('page1Ctrl', function(pageFactory) {
  var vm = this;

  // page dependent
  vm.name = 'theOne';
  vm.service = 'oneService';
  vm.seriesLabels = ['One1', 'Two1', 'Three1'];

  // these variables are declared in all pages
  // directive variables,
  vm.date = {
    date: new Date(),
    dateOptions: {
      formatYear: 'yy',
      startingDay: 1
    },
    format: 'dd-MMMM-yyyy',
    opened: false
  };

  vm.open = function($event) {
    vm.date.opened = true;
  };

  // dataservice
  vm.data = []; // the structure can be different but still similar enough
  vm.update = function() {
      vm.data = pageFactory.get(vm.service);
    }

  //default call
  vm.update();   
})
Run Code Online (Sandbox Code Playgroud)

基本上我把所有的逻辑都移到了工厂和指令上.但现在在每个使用我需要的指令的控制器中,例如,一个保持指令正在修改的值的字段.这是设置.后来我需要类似的字段来保存来自dataservice的数据,并且调用本身(方法)也是一样的.

这导致了很多重复.


从图形上看,我看到当前示例如下所示:

目前的设计

虽然我认为正确的设计看起来应该更像这样:

预期的设计


我试图在这里找到一些解决方案,但似乎都没有得到证实.我发现了什么:

  1. AngularJS DRY控制器结构,建议我传递$ scope或vm并用额外的方法和字段来装饰它.但许多消息人士称这是肮脏的解决方案
  2. 扩展AngularJS控制器的推荐方法是什么?使用angular.extend,但这在使用controller as语法时有问题.
  3. 然后我也找到了答案(在上面的链接中):

您不扩展控制器.如果它们执行相同的基本功能,则需要将这些功能移动到服务中.该服务可以注入您的控制器.

即使我做了,仍然有很多重复.或者它是否必须如此?像John Papa sais(http://www.johnpapa.net/angular-app-structuring-guidelines/):

尽量保持干燥(不要重复自己)或T-DRY

你有没有遇到类似的问题?有什么选择?

jjb*_*kir 8

从整个设计的角度来看,我没有看到装饰控制器和扩展控制器之间有太大区别.最后,这些都是mixins而不是继承的形式.因此,它真正归结为您最熟悉的工作.其中一个重要的设计决策不仅仅涉及如何将功能传递给所有控制器,还包括如何传递功能来说明3个控制器中的2个.

工厂装饰

正如您所提到的,实现此目的的一种方法是将$ scope或vm传递到工厂,该工厂使用额外的方法和字段来装饰您的控制器.我不认为这是一个肮脏的解决方案,但我可以理解为什么有些人会想要将工厂从他们的范围中分离出来以分离他们代码的问题.如果您需要为3个场景中的2个添加其他功能,则可以传入其他工厂.我做了一个更好的例子.

dataservice.js

routerApp.factory('pageFactory', function() {

    return {
      setup: setup
    }

    function setup(vm, name, service, seriesLabels) {
      // page dependent
      vm.name = name;
      vm.service = service;
      vm.seriesLabels = seriesLabels;

      // these variables are declared in all pages
      // directive variables,
      vm.date = {
        date: moment().startOf('month').valueOf(),
        dateOptions: {
          formatYear: 'yy',
          startingDay: 1
        },
        format: 'dd-MMMM-yyyy',
        opened: false
      };

      vm.open = function($event) {
        vm.date.opened = true;
      };

      // dataservice
      vm.data = []; // the structure can be different but still similar enough
      vm.update = function() {
        vm.data = get(vm.service);
      }

      //default call
      vm.update();
    }

});
Run Code Online (Sandbox Code Playgroud)

page1.js

routerApp.controller('page1Ctrl', function(pageFactory) {
    var vm = this;
    pageFactory.setup(vm, 'theOne', 'oneService', ['One1', 'Two1', 'Three1']);
})
Run Code Online (Sandbox Code Playgroud)

扩展控制器

您提到的另一个解决方案是扩展控制器.这可以通过创建一个混合到正在使用的控制器的超级控制器来实现.如果您需要为特定控制器添加其他功能,您可以将其他超级控制器与特定功能混合使用.这是一个有关的例子.

ParentPage

routerApp.controller('parentPageCtrl', function(vm, pageFactory) {

    setup()

    function setup() {

      // these variables are declared in all pages
      // directive variables,
      vm.date = {
        date: moment().startOf('month').valueOf(),
        dateOptions: {
          formatYear: 'yy',
          startingDay: 1
        },
        format: 'dd-MMMM-yyyy',
        opened: false
      };

      vm.open = function($event) {
        vm.date.opened = true;
      };

      // dataservice
      vm.data = []; // the structure can be different but still similar enough
      vm.update = function() {
        vm.data = pageFactory.get(vm.service);
      }

      //default call
      vm.update();
    }

})
Run Code Online (Sandbox Code Playgroud)

page1.js

routerApp.controller('page1Ctrl', function($controller) {
    var vm = this;
    // page dependent
    vm.name = 'theOne';
    vm.service = 'oneService';
    vm.seriesLabels = ['One1', 'Two1', 'Three1'];
    angular.extend(this, $controller('parentPageCtrl', {vm: vm}));
})
Run Code Online (Sandbox Code Playgroud)

嵌套状态UI-Router

由于您使用的是ui-router,因此您还可以通过嵌套状态获得类似的结果.有一点需要注意的是$ scope不会从父控制器传递给子控制器.因此,您必须在$ rootScope中添加重复的代码.当我想要传递整个程序的函数时,我会使用它,例如测试我们是否在移动电话上的功能,不依赖于任何控制器.这是一个有关的例子.