AngularJS:何时将$ scope变量传递给function

时间:2013-09-23 19:21:14

标签: javascript angularjs

我使用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 而不是传入它。

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

6 个答案:

答案 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,您会注意到类似的东西:框架尝试将参数注入动作函数,而不是直接从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 :(得分: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)

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,其执行顺序是明确的:在值更改后,它将仅执行。

答案 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)

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