24

我正在使用TodoMVC应用程序来更好地使用 AngularJS 框架。在第 14-16 行的index.html中,您会看到:

<form id="todo-form" ng-submit="addTodo()">
    <input id="new-todo" placeholder="What needs to be done?" ng-model="newTodo" autofocus>
</form>

注意ng-submit指令如何调用addTodo()函数,而没有将newTodo模型作为参数传递。

不久之后,我在第 19 行的同一个文件中遇到了以下代码:

<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-click="markAll(allChecked)">

可以看到作者这次决定将allChecked模型传递给markAll()函数。如果我理解正确,他们可以在控制器内部引用$scope.allChecked而不是将其传入。

为什么在同一个文件中使用两种不同的方法?在某些情况下,一种方法更好吗?这是不一致的情况还是使用了更深层次的逻辑?

4

6 回答 6

32

我宁愿总是将参数传递给函数:

  • 函数期望的参数更清楚。
  • 单元测试更容易,因为所有参数都被注入到函数中。(有利于单元测试)

考虑以下情况:

$scope.addToDo = function(){
   //This declaration is not clear what parameters the function expects.
   if ($scope.parameter1){
      //do something with parameter2
   }    
}

更糟糕的是:

$scope.addToDo = function(){
    //This declaration is not clear what parameters the function expects.
    if ($scope.someobject.parameter1){ //worse

    }    
}

由于作用域继承parameter2可能来自父作用域,parameter2因此在函数内部访问会产生紧密耦合,当您尝试对该函数进行单元测试时也会造成麻烦。

如果我这样定义函数:

//It's clearer that the function expects parameter1, parameter2
$scope.addToDo = function(parameter1, parameter2){
   if (parameter1){
      //do something with parameter2
   }    
}

如果您parameter2是从父范围继承的,您仍然可以从视图中传递它。当您进行单元测试时,很容易传递所有参数。

如果您曾经使用过 ASP.NET MVC,您会注意到类似的事情:框架尝试将参数注入到动作函数中,而不是直接从RequestHttpContext对象访问它

如果其他人提到喜欢与ng-repeat

在我看来,Angular 中的 Controller 和 Model 并没有完全分开。$scope 对象看起来像我们的带有属性和方法的模型(模型也包含逻辑)。有OOP背景的人会认为:我们只传入不属于对象的参数。就像 Person 已经拥有的类一样hands,我们不需要hands为每个对象方法传入。像这样的示例代码:

//assume that parameter1 belongs to $scope, parameter2 is inherited from parent scope.
    $scope.addToDo = function(parameter2){ 
        if ($scope.parameter1){ //parameter1 could be accessed directly as it belongs to object, parameter2 should be passed in as parameter.
            //do something with parameter2
        }   
    }
于 2013-10-05T08:50:06.260 回答
9

这个答案有两个部分,第一部分是回答哪个是更好的选择,另一部分是它们都不是一个好的选择!


哪一个是正确的?

这个是:

$scope.addToDo = function(params1, ...) {
    alert(params1);
}

为什么?因为 A - 它是可测试的。即使您不编写测试,这也很重要,因为从长远来看,可测试的代码几乎总是更具可读性和可维护性。

由于 B,它也更好 - 它对于调用者是不可知的。此功能可以被任意数量的不同控制器/服务/等重用,因为它不依赖于范围的存在或该范围的结构。

当您改为这样做时:

$scope.addToDo = function() {
    alert($scope.params1);
}

A 和 B 都失败了。它本身不容易测试,也不容易被重用,因为你使用它的范围可能有不同的格式。

编辑:如果您正在做与您的特定范围密切相关的事情并从模板运行函数,那么您可能会遇到试图使其可重用的情况没有意义。该功能根本不是通用的。在这种情况下,请不要为此烦恼,某些功能无法重用。查看我所写的作为您的默认模式的内容,但请记住,在某些情况下它不适合。


为什么两个都错了?

因为作为一般规则,您不应该在控制器中执行逻辑,这是服务的工作。控制器可以使用服务并调用该函数或在模型中公开它,但不应定义它。

为什么这很重要?因为它再次使重用该功能变得容易。在控制器中定义的函数不能在另一个控制器中重用,除非限制控制器在 HTML 中的调用方式。在服务中定义的函数可以在任何你喜欢的地方注入和重用。

但我不需要重用该功能!- 是的你是!也许不是现在,也许永远不会用于这个特定的功能,但迟早你会想要重用一个你确信永远不需要重用的功能。然后你将不得不重新编写你已经忘记一半的代码,这总是需要额外的时间。

最好从一开始就正确地做这件事,并将所有你可以做的逻辑转移到服务中。这样,如果您在其他地方(甚至在另一个项目中)需要它们,您可以抓住它并使用它,而无需重写它以适应您当前的范围结构。

当然,服务不知道你的范围,所以你不得不使用第一个版本。奖金!并且不要陷入将整个范围传递给服务的诱惑,这将永远不会结束:-)

所以这是 IMO 最好的选择:

app.service('ToDoService', [function(){
    this.addToDo = function(params1, ...){
        alert(params1);
    }
}]);

在控制器内部:

$scope.addToDo = ToDoService.addToDo;

请注意,我写的是“一般规则”。在某些情况下,在控制器本身而不是服务中定义功能是合理的。一个例子是当函数只与范围特定的事物相关时,比如以某种方式切换控制器中的状态。没有真正的方法可以在服务中做到这一点而不会让事情变得奇怪。

但听起来这里不是这种情况。

于 2013-10-07T13:25:42.677 回答
9

Angular的禅宗建议:

在模板中将范围视为只读
将范围视为仅在控制器中写入

遵循此原则,您应该始终使用模板中的参数显式调用函数。

但是,在您遵循的任何样式中,您都必须小心priorities指令的执行顺序和执行顺序。在您的示例中,使用ng-modelandng-click将两个指令的执行顺序保留为 ambiguous。解决方案是 using ,其中执行顺序很明确:只有值更改后ng-change才会执行。

于 2013-12-14T10:34:42.343 回答
4

自定义行为方法,例如 ng-click、ng-submit 等,允许我们将参数传递给被调用的方法。这很重要,因为我们可能想要传递一些在控制器中的处理程序之前可能无法自由使用的东西。例如,

angular.module('TestApp')
.controller('TestAppController', ['$scope', function($scope) {
    $scope.handler = function(idx) {
        alert('clicked ' + idx.toString());
    };
}]);

<ul>
    <li ng-repeat="item in items">
        <button ng-click="handler($index)">{ item }</button>
        <!-- $index is an iterator automatically available with ngRepeat -->
    </li>
</ul>

在您的第二个示例的情况下,由于allChecked在定义的同一控制器的范围内markAll(),您是绝对正确的,不需要传递任何东西。我们只是在创建另一个副本。

该方法必须简单地重构以使用范围内可用的内容。

$scope.markAll = function () {
    todos.forEach(function (todo) {
        todo.completed = $scope.allChecked;
    });
};

因此,即使我们可以选择在这些方法中使用参数,它们也只是在某些时候需要。

于 2013-10-04T10:18:53.743 回答
2

我认为这只是代码不一致的情况。我之前思考过这个问题并得出以下结论......

规则:不要将 $scope 变量传递给 $scope 函数。

阅读控制器代码应该足以演示组件的功能。视图不应包含任何业务逻辑,仅包含绑定(ng-model、ng-click 等)。如果视图中的某些内容可以通过移动到控制器来变得更清晰,那就这样吧。

例外:允许有条件的 ng-class 语句(例如ng-class='{active:$index==item.idx') - 将类条件放在控制器中可能非常冗长,并且会使控制器的逻辑与视图中的想法混淆。如果它是视觉属性,请将其保留在视图中。

例外:您正在处理 ng-repeat 中的项目。例如:

<ul ng-repeat="item in items">
    <li><a ng-click="action(item)"><h1>{{item.heading}}</h1></a></li>
</ul>

我在编写控制器和视图时遵循这些规则,它们似乎工作。希望这可以帮助。

于 2013-10-03T00:21:09.537 回答
1

也许是为了说明你可以?假设它们是同一个控制器,两者之间没有功能差异。请注意,在某些情况下会生成子作用域,在这种情况下,您将不再具有与控制器相同的作用域。

于 2013-09-23T19:28:02.187 回答