【问题标题】:Keep angular controller thin保持角度控制器薄
【发布时间】:2015-07-28 07:44:23
【问题描述】:

此时我正在处理巨大的角度 SPA 应用程序。我尽量让我的控制器保持纤薄:

<div ng-controller='HomeController as home'>
   <div ng-repeat='var item in home.items' ng-bind='item' ></div>
   <button ng-click='home.remove(1)' >remove</button>
</div>

function HomeController (homeService){
    var vm = this; //$scope
    vm.items = [1,2,3,4,5];
    vm.remove = remove;

    function remove (id){
        homeService.remove({ items: vm.items, targetId: id });
    }

    //a lot of other logic here
}


angular.module('my').service('homeService', homeService);
function homeService (){
    this.remove = remove;

    function remove (param){
        for(var a = 0; a < param.items.length; a++){
            if(param.items[a] == param.targetId){
                param.items.splice(a, 1);
                break;
            }
        }
    }
}

优点:

  • 逻辑在控制器之外

缺点:

  • 服务更改范围状态

您有什么方法可以让控制器保持精简?

【问题讨论】:

  • “服务变更范围状态”是什么意思?
  • @DmitriZaitsev,我编辑了我的代码,我只想说,在服务中更改了 var 项,此更改将影响 ui。
  • 服务不应该知道 ui!
  • 是的,这是主要问题。请问,你是如何在你的控制器中组织代码的,或者有什么建议可以改进上面的代码吗?
  • 我建议将代码发布到codereview.stackexchange.com 以获得深入的建议

标签: angularjs model-view-controller


【解决方案1】:

您有什么方法可以让控制器保持精简?

我个人喜欢在工厂/服务中保留与应用程序模型相关的任何内容。因此,您的代码中的remove 和item 不会在控制器中定义。在控制器内部,我将设置对模型的引用,以获取指令可用的任何内容,即通过$scope 访问。

例如,考虑一个具有实体数组的模型以及在数组中添加/删除/查找实体的方法。我将首先创建一个工厂来公开我的模型和使用它的方法:

angular.module('myApp').factory('model', function() {

    // private helpers
    var add = function(array, element) {...}
    var remove = function(array, element) {...}
    var find = function(array, id) {...}

    return {
        Entity: function(id) {
            this.id = id;
        },
        entities: {
            entities: [],
            find: function(id) {
                return find(this.entities, id);
            },
            add: function(entity) {
                add(this.entities, entity);
            },
            remove: function(entity) {
                remove(this.entities, entity);
            }       
        }
});

然后将模型传递给我的控制器:

angular.module('myApp').controller('ctrl', function($scope, model) {
    $scope["model"] = model; // set reference to the model if i have to
    var entity = new model.Entity('foo'); // create a new Entity
    model.entities.add(entity); // add entity to entities
    model.entities.find('foo'); // find entity with id 'foo'
});

等等

【讨论】:

  • 您可能需要将.Entity 重命名为.getEntityById 或.getById 以使其更具可读性。还命名.Entity 违反了标准的驼峰式约定,我看不出有什么原因。
  • model.Entity 函数不是检索/getter 函数,而是要在控制器中实例化的类,如new model.Entity('id')。因此我使用了大写。
【解决方案2】:

我在您的示例中错过的第一件事是指令。指令是一个强大的 Angular 工具,它允许您重用代码、封装和公开 html 中的行为。为了使控制器保持精简,您需要将逻辑拆分为指令和服务。我会用类似的东西写你的例子:(不是工作代码,我写了一些东西来说明分割逻辑的想法)

// "home"
<div ng-controller='HomeController as home'>
   <my-item ng-repeat='var item in home.items' ng-bind='item'></my-item>
</div>

// itemtemplate.html
<div>
    {{ item.name }}
    <button ng-click='remove()' >remove</button>
</div>

function HomeController (homeService){
    var vm = this; //$scope
    vm.items = homeService.items;

    // The idea here is to make this controller 
    // only needed to load content for this route, 
    // all the other logic should be in the directives and services 
}

angular.module('my').directive('myItem', funcion(homeService){
    return {
        restrict: 'E',
        templateUrl: 'itemtemplate.html',
        controller: function(scope, element, attrs) {
            scope.remove = function(){
                homeService.remove(scope.item);
            }
        } 
    }  
})

angular.module('my').factory('homeService', homeService);
function homeService (){

    var items = [];

    return { 
        loadItems: function() {
            items = [... pick the items from server or whatever ...];
        },

        remove: function() {
            for(var a = 0; a < this.items.length; a++){
                if(this.items[a] == this.targetId){
                    this.items.splice(a, 1);
                    break;
                }
            }
        }
    };
}

我还将 items 数组移动到服务中(我还为工厂更改了它,因为我认为每次注入都会实例化服务,并且您需要让每个人都可以使用这些项目),这样可以从其他控制器访问它或指令,控制器不必关心它。

【讨论】:

    【解决方案3】:

    我更喜欢不仅使用瘦控制器,还喜欢从控制器本身更改控制器状态。

    function Ctrl($scope, Service) {
         var ctrl = this;
         $scope.ctrl = ctrl;
         Service.getData().then(function() {
              ctrl.data = Service.data;
         })
    }
    
    function Service ($http) {
        var data = [];
        this.getData = function() {
             return $http.get().then(function(res) {
                  data = res;
             });
        }  
    }
    

    此构造导致应用程序数据流以一种方式定向。因此,我始终确定何时刷新数据,并且始终可以跟踪数据的来源。

    当然,我做了一些排除。例如。如果您不需要数据持久性(数据应在 $destroy 上删除),我将在控制器中保存状态。

    我也喜欢将服务拆分为视图服务和数据服务,其中视图服务负责视图状态,如详细/列表视图、类等。

    我也喜欢使用自己的事件总线来避免使用监视表达式和广播/发射

    最有用的是将所有标志(如 $scope.viewType、$scope.pageClass 等)移动到单独的指令中。这将导致代码具有极高的可读性

    【讨论】:

      【解决方案4】:

      您为什么使用var vm = this;?你知道this 会指向实例控制器吗?其中$scope 有两种数据绑定方式。同样要从数组中删除元素,可以用一两行来完成。在控制器中删除它似乎是合法的,imo。

      请查看我的 jsfiddle:http://jsfiddle.net/HB7LU/13841/

      【讨论】:

      • 嗨,关于“vm = this”,您可以在这里阅读github.com/johnpapa/angular-styleguide#style-y031。 “也可以从数组中删除元素,它可以用一两行来完成。” - 我故意用简单的例子,而不是删除你可以想象的任何其他复杂逻辑的逻辑。
      • @mola10 你说的缺点:服务改变范围状态是什么意思?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-08-15
      • 2013-08-16
      • 1970-01-01
      • 2014-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多