AngularJS:为什么不在控制器中编写逻辑?

时间:2015-07-16 12:41:40

标签: angularjs angularjs-directive standards angularjs-controller

请原谅我,如果这听起来很愚蠢,但我现在已经使用AngularJS了一段时间我见过人们告诉我将我的逻辑包装在一个指令(或服务?)而不是我的控制器并只保留我的控制器中的绑定。除了指令的可重用性方面还有其他原因吗?

到目前为止,我还没有真正理解为什么会这样。不写指令会带来很多开销吗?我没有遇到任何在我的控制器中编写逻辑的问题,而且很容易。 这种方法的缺点是什么?

4 个答案:

答案 0 :(得分:13)

控制器是完成所有操作以及与范围相关的所有操作的正确位置。这是你写所有

的地方
$scope.$watch(...)

并定义您需要从视图中访问的所有$scope函数(如事件处理程序)。通常,事件处理程序是计划函数,它将依次将函数称为服务。

$scope.onLoginButtonClick = function(){
    AuthenticationService.login($scope.username,
        $scope.password);
};

在极少数情况下,你可以在那里添加一个promise成功处理程序。

DONT:在控制器中编写业务逻辑

有一个非常具体的原因,为什么早期的例子是这样的。它向您显示了$scope函数,该函数又调用服务中的函数。控制器不对登录机制或登录方式负责。如果你在服务中编写这个代码,你就会将服务与控制器分离,这意味着你想要使用相同服务的任何其他地方,你需要做的就是注入并解雇该函数。

未来控制器的规则:

  • 控制器应保持零逻辑 控制器应该只引用模型的引用(以及从promises返回的调用方法)
  • 控制器只能将逻辑结合在一起
  • 控制器驱动模型更改和查看更改。关键词;驱动器,而不是创建/持久,它会触发它们!
  • 委托更新工厂内部的逻辑,不解析Controller内部的数据,只更新控制器的值,使用更新的工厂逻辑,这样可以避免控制器上的重复代码以及工厂测试变得更容易
  • 保持简单,我更喜欢XXXXCtrl和XXXXFactory,我确切地知道这两个人做了什么,我们不需要花哨的名字
  • 保持方法/道具名称在共享方法中保持一致,例如this.something = MyFactory.something;否则就会变得混乱
  • 工厂持有模型,更改,获取,更新和持久化模型更改
  • 将Factory视为一个需要持久化的对象,而不是在Controller中持久化
  • 与工厂内的其他工厂交谈,让他们远离控制器(成功/错误处理等)
  • 尽量避免将$ scope注入控制器,通常有更好的方法来做你需要的事情,比如避免$ scope。$ watch()

答案 1 :(得分:3)

有两个很好的理由让我将逻辑从控制器中移除:

复用性

如果你的应用程序有多个控制器,并且每个控制器都有一些差异,那么在控制器中保留逻辑意味着你将重复你编写的代码。如果你Don't Repeat Yourself,那就更好了。通过将该逻辑放入服务中,您可以将相同的代码注入多个控制器。每次将每个服务(实际上为Factory)创建为自身的新实例,每次将其注入控制器。通过将逻辑推送到服务中,您可以modularise代码,这样可以更容易维护和测试(见下文)

测试

测试好的代码。不只是由人,而是由你写的单元测试。单元测试可以让您作为开发人员确保您的代码也能达到您的预期。它们还可以帮助您很好地设计代码。

如果您的控制器有20种不同的方法,每种方法都有自己的逻辑,那么测试(和您的代码)就会变成意大利面。

编写狭窄的单元测试更容易,即他们一次测试一件事。幸运的是,将上面的代码分解为封装的部分也很好(因为上面提到的原因),即他们做了一件事并且可以单独完成。所以单元测试(特别是如果你先编写测试)会迫使你思考如何将你的代码分解为可维护的部分,如果你想在未来进行更改,你的应用程序就会处于良好状态(你运行单元测试)并且可以看到事情在哪里破裂)。

实施例

表格申请:

您有一个表单应用程序服务多个表单。每个表单都有一个控制器。当用户提交表单时,数据通过代理发送到CRM,该CRM将信息存储在数据库中。

如果CRM中已存在客户,则您不希望创建重复项(是的,CRM应该处理数据清理,但您希望尽可能避免这种情况)。因此,一旦用户提交表单数据,就需要实现类似以下内容的逻辑:

  • 通过API端点在CRM中搜索用户
  • 如果用户存在,则获取用户ID并将表单数据传递给另一个端点
  • 如果它们不存在则触及另一个端点并创建新用户,获取用户ID并将其发送并将表单数据与用户关联

注意:可以说上述所有内容都应该通过后端服务来完成,但为了举个例子,让我们继续使用它。

您的申请表有多种形式。如果你为每个表单的每个控制器硬编码相同的逻辑(是的,你应该每个表单有一个控制器,即每个视图一个),那么你会多次重复自己。编写需要检查控制器的测试可以完成基础工作(发布数据,管理对视图的更改),还可以测试每个控制器的所有逻辑。

或者将该逻辑写入一次,将其放入服务中,为其编写一个测试并将其注入任何您喜欢的位置。

参考

查看Angular documentation,看看Angular实现的模式以及为什么这些模式可以遵循(Design Patterns - 大模块,依赖注入,工厂和单例)。

答案 2 :(得分:2)

控制器的最大问题是你没有定义它所使用的html。

使用时......

<div ng-controller="myController"></div>

...然后你必须在你的控制器中注入你的html,这是基本的老式jQuery思维。

使用时......

<div ng-controller="myController">... some html ...</div>

...你的指令及其工作的html在不同的地方定义。也不是你想要的。

使用指令强制您将您的html和它所需的代码放在同一个地方。由于该指令也有自己的范围,因此不会对代码中其他地方的其他变量产生任何干扰。如果您需要来自其他地方的变量,您还必须明确地注入它们,这也是一件好事。

我使用的这个词为什么这是一个好东西是'原子'但我不确定这是否是正确的词。含义:所有应该一起工作的东西都在一个文件中。使用templateUrl,这不再完全正确,模板仍然在指令中定义。

所以在我的控制器中没有代码可以对dom做任何事情。只是最简单的一些页面/视图计数代码,或将API数据连接到作用域,或使用$ routeParam数据执行某些操作。所有其他代码都放在服务/工厂(业务逻辑)或指令(dom逻辑)中。

顺便说一句:可以为你的指令定义一个控制器,但通常只用于'指令间通信'(所以它们可以共享状态),但你只能将它与指令一起使用(如tab选项中重复的tab指令。)

答案 3 :(得分:-1)

您不在控制器中编写逻辑的主要原因是$scopes在路由更改时使用controller收集垃圾中的所有$destroy()。在收到ngView广播的$routeChangeSuccess指令中,有一个函数只保留当前活动视图的$ scope,所有其他$ scope都被销毁。

因此,例如,如果您有购物车应用程序且您的业务逻辑是使用$ scopes的控制器,则用户将丢失产品和已在订单页面上输入的所有表单数据(如果他们使用后退按钮等)