2013-09-23 54 views
22

我正在使用TodoMVC应用程序来更好地使用AngularJS框架。在index.html上线14-16看到这一点:

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

通知的NG提交指令如何调用addTodo()未经newTodo模型作为参数传递功能。

很短的时间后,我在同样的文件,遇到下列代码来到第19行:

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

你可以看到笔者决定通过allChecked模式向markAll()这一次功能。如果我理解正确的话,他们可以参考$ scope.allChecked控制器内部,而不是通过它的。

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

+1

这通常是一个范围问题。如果使用为每次迭代创建子作用域的ng-repeat,则需要将实例变量作为参数传递。否则,你不知道它是什么。除此之外,我会说这只是一个偏好和易于写作的问题。 –

+0

我做了一些关于[这篇文章]范围继承的研究(http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs/14049482 #140494820)。为了澄清我的具体问题,我正在寻找一些关于何时使用这种或那种方法的最佳实践。如果它们相同,假设范围相同,那么我会接受该答案。 –

回答

30

我宁愿永远参数传递给函数:

  • 它更清晰什么参数的函数需要。
  • 因为所有参数注入功能它更容易单元测试(好单位 - 测试)

考虑以下情况:

$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

在我看来,控制器和模型在角度不明确分开。 $ 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

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

2

我认为这只是代码中不一致的情况。我之前曾经思考过这个问题,并得出如下结论:

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

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

例外:允许有条件纳克级的语句(例如ng-class='{active:$index==item.idx') - 把类条件句的控制器可以是非常冗长,muddies控制器的逻辑与来自视图的想法。如果它是一个可视属性,请将其保存在视图中。

例外:您正在与项目工作在NG-重复。例如:

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

我遵循这些规则写控制器时&意见,他们似乎工作。希望这可以帮助。

+2

我同意保持控制器中的逻辑,而不是将$ scope变量传递到$ scope函数中,但是这不会减少单元测试代码的能力,尤其是函数吗?考虑一个函数displayMessage()与displayMessage(errMsg)相比较。第二个版本在我看来更容易测试。第一个版本依赖于实施细节和假设。你的答案似乎是一个好的开始,但我认为它缺少关于在遵守这些其他规则的同时保持代码可测试性的讨论。 –

+0

@亚当托马斯:是的,我用更多的信息更新了我的答案。 '如果你曾经使用ASP.NET MVC,你会注意到一些类似的东西:框架试图将参数注入到action函数中,而不是直接从Request或HttpContext对象中访问它。 –

+0

@AdamThomas我不相信这确实使代码更少测试。 $ scope的内容定义了控制器的状态,$ scope方法的行为方式取决于控制器的状态。另一点我会做的是,对于任何复杂的事情,你的方法可能需要考虑许多不同的变量来决定它的行为。带有大量参数的IMO函数不太可读,在这种情况下,函数直接从$ scope读取属性会更好。 –

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; 
    }); 
}; 

因此,即使我们在这些方法中使用的参数的选择,他们只需要一些时间。

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; 

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

但这听起来似乎并非如此。

+0

非常赞同。现在,人们倾向于将业务逻辑放入模型中,该模型是具有特定于控制器的服务的模块。在模块内部,它可以调用其他服务提供的常用方法。 –

+1

我不同意。与控制器紧密耦合的视图逻辑应位于控制器中。有时候,当显示/启用/禁用时,你有很多依赖关系的复杂接口。为什么只有在这个控制器中创建使用sesnse的服务? – Styx

+0

那么我同意你:)正如我在最后一部分所说的,服务并不总是正确的答案,而不是当它直接与控制器相关时。在这种情况下,就像你说的那样,为什么要提供服务时,它实际上不能被重用,因为它取决于控制器和视图。 –

9

Zen of Angular提示:

 
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,其中执行的顺序是明确的:它将在之后的值仅发生变化