我使用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 指令如何在不将 newTodo 模型作为参数传递的情况下调用 addTodo()函数。
不久之后,我在第19行的同一个文件中遇到了以下代码:
<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-click="markAll(allChecked)">
您可以看到作者此次决定将 allChecked 模型传递给 markAll()函数。如果我理解正确,他们可以在控制器中引用 $ scope.allChecked 而不是传入它。
为什么在同一个文件中使用两种不同的方法?在某些情况下,一种方法更好吗?这是一个不一致的情况还是使用了更深层次的逻辑?
答案 0 :(得分:31)
我更愿意总是将参数传递给函数:
考虑以下情况:
$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,您会注意到类似的东西:框架尝试将参数注入动作函数,而不是直接从Request
或HttpContext
对象
如果其他人提到过使用ng-repeat
在我看来,角度控制器和模型并没有很清楚地分开。 $ scope对象看起来像我们的Model,带有属性和方法(Model也包含逻辑)。来自OOP背景的人会认为:我们只传递不属于对象的参数。就像类已经有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
}
}
答案 1 :(得分: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;
请注意,我写了“一般规则”。在某些情况下,在控制器本身而不是服务中定义函数是合理的。一个例子是当函数仅涉及范围特定事物时,例如以某种方式切换控制器中的状态。在没有变得奇怪的情况下,没有真正的方法可以在服务中做到这一点。
但听起来并非如此。
答案 2 :(得分:9)
Treat scope as read only in templates Treat scope as write only in controllers
遵循这个原则,你应该始终使用模板中的参数调用函数明确。
但是,在您遵循的任何风格中,您必须注意priorities
和指令的执行顺序。在您的示例中,using ng-model
and ng-click
leaves the order of execution of the two directives ambiguous。解决方案是使用ng-change
,其执行顺序是明确的:在值更改后,它将仅执行。
答案 3 :(得分: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;
});
};
因此,即使我们可以选择在这些方法中使用参数,但只需要一些时间。
答案 4 :(得分: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>
我在编写控制器时遵循这些规则&amp;观点,他们似乎工作。希望这会有所帮助。
答案 5 :(得分:1)
或许可以说明你可以吗?两者之间没有功能差异,假设它们是相同的控制器。请注意,在某些情况下会生成子范围,在这种情况下,您将不再具有与控制器相同的范围。