在控制器中使用Angular的$watch是一种反模式吗?

30

我一直在努力按照AngularJS的正确方式去做事情,目前正在阅读如何让控制器观察Angular服务中保存的模型变化的相关内容。

有些网站表示,在控制器中使用$watch是绝对错误的:

不要在控制器中使用$watch。它很难测试,并且在几乎所有情况下都是完全不必要的。使用作用域上的方法来更新被$watch修改的值。

其他人则认为只要清理干净就可以使用$watch:

$watch函数本身返回一个函数,该函数将在调用时取消$watch的绑定。因此,在不再需要$watch时,我们只需调用$watch返回的函数。

SO问题其他知名网站明确表示,在控制器中使用$watch是注意到angular-service维护的模型变化的好方法。

https://github.com/angular/angular.js/wiki/Best-Practices网站,我认为我们可以更加重视它,直言$ scope.$watch应该替代事件的需要。然而,对于处理100个以上模型和REST端点的复杂SPA,选择使用$watch避免具有$broadcast/$emit事件可能会产生大量的观察者。另一方面,如果我们不使用$watch,对于非平凡的应用程序,我们最终会得到许多事件纷乱。

这是一个失败/失败的情况吗?事件和观察之间是一个虚假的选择吗?我知道您可以在许多情况下使用双向绑定,但有时您只需要需要一种监听更改的方式。

编辑

Ilan Frumer的评论让我重新考虑了我的问题,所以也许不仅仅是问在控制器中使用$watch是否主观上好/坏,让我这样提问:

哪种实现方式可能首先创建性能瓶颈?将控制器设置为监听事件(必须已经广播/发出),还是在控制器中设置$watch。请记住,这是一个大型应用程序。

哪种实现方式首先会带来维护方面的麻烦:$watch还是事件?可以说无论哪种方式都存在耦合(紧密/松散)...事件观察者需要知道要监听什么,而对外部值(例如MyDataService.getAccountNumber())进行$watch需要了解其$scope之外发生的事情。

** 一年后的编辑 **

Angular自我提高和改进很多,但我仍然得到了+1,所以我想提一下,在查看angular团队的代码时,我发现当涉及到控制器中的观察者(或者有一个被销毁的作用域的指令)时,存在一种模式:

$scope.$on('$destroy', $scope.$watch('scopeVariable', functionIWantToCall));
这个代码段的作用是获取 $watch 函数返回的函数(可以调用以取消监视器),并将其提供给控制器销毁时的事件处理程序。这会自动清除监视器。
无论在控制器中使用监视器是否是一种代码异味,如果您使用它们,我相信 Angular 团队对此模式的使用应该作为如何使用它们的强烈推荐。
谢谢!

2
我认为你在最后一句话中已经回答了自己的问题。如果可以使用双向绑定,请使用它,但如果无法解决问题,请利用$watch。 - zszep
1
你能举个“有时你只是需要一种监听变化的方式”的例子吗? - calebboyd
这是一个非常好的问题,我需要几个小时才能回答它。希望它不会被关闭为“基于主观看法的主要观点”。 - Ilan Frumer
@calebboyd 当然可以!我有一个控制器,需要在检测到新的ui-router状态时更新其视图。我可以$watch一个启动状态更改的服务,或者使用$scope.$on('$stateChangeStart', fn()...)监听事件。我没有找到一种干净的方法来使用数据绑定来注意服务中的更改,该服务触发状态更改,因为数据绑定发生在首先,而状态更改尚未发生。尽管听起来很丑陋,但最简单的解决方法是监听$stateChangeStart事件,然后启动$digest... 恐怖 - tengen
3个回答

5
我同时使用这两种方法,因为我认为它们是解决不同问题的不同工具。
以下是我构建的一个应用程序的示例。我有一个复杂的WebSocket服务,它从WebSocket服务器接收动态数据模型。服务本身并不关心模型的样子,但是控制器很在意。
当控制器被初始化时,它会在服务数据对象上设置一个$watch,以便知道它的特定数据对象何时到达(就像等待Service.data.foo存在一样)。一旦该模型出现,它就能够绑定到它并创建一个双向数据绑定,监视变得过时并被销毁。
另一方面,服务也负责广播某些事件,因为有时客户端会从服务器接收到字面命令。例如,服务器可能请求客户端发送存储在整个应用程序中的“$rootScope”中的某些元数据。在module.run()步骤中,在$rootScope中设置一个.on()来监听来自服务器的这些命令,从其他服务中收集所需的信息,并调用WebSocket服务返回请求的数据。或者,如果我使用$watch()来做到这一点,我将需要设置某种任意变量供其观察,例如metadataRequests,每次收到请求时都需要增加它。广播可以实现相同的功能,而无需像变量那样永久驻留在内存中。
基本上,当我想要看到特定值发生变化时(尤其是如果我需要知道变化前后的值),我使用$watch();而如果控制器需要知道已满足更高级别的条件,则使用事件。关于性能,我无法告诉你哪一个会首先成为瓶颈,但我觉得这样考虑可以让您在它们最擅长的地方使用每个功能的优点。例如,如果您使用$on()而不是$watch()来查找数据更改,您将无法访问更改之前和之后的值,这可能会限制您要做的事情。

2
我最终选择了这条路线,这也与@calebboyd发布的相似:每个都有它的使用场所。但你的回答给了我两件事情... 当旧值和新值同时存在时使用$watch会很方便,并且使用服务进行广播。我的实现是让服务和控制器双向绑定,并让服务监听一些特定的事件,而不是让所有的控制器监听事件并轮询服务以获取新数据。这为我节省了大量的代码...谢谢。 - tengen

3
所有双向数据绑定实际上都是在任何你给予ng-model的作用域属性上放置一个$watch。该作用域属性有一个控制器,允许其他指令如inputform将值同步到ng-model上,从而在更改时呈现视图。这是通过DOM中事件的注册检测到的。简而言之,ng-model$watch比较了模型中的值与其内部的值。它内部的值是由支持指令(例如 input,form 等)设置的。在 Angular 应用程序中,我认为您应该对唯一的“事件”做出反应即用户创建的 DOM 事件。这些可以通过 DOM 上的指令以及ng-model链接到模型来解决。此外,自然还存在异步操作,对于此,Angular 提供了$q,其中回调会触发$digest
至于性能,Angular 文档中已经很好地阐述了它。它在每个$digest上运行。所以要让它快速。什么是每个$digest?Angular 遍历所有活动范围。每个范围都有监视器,它们会被执行,并在其中执行比较。如果有差异,它将再次运行(下一轮)。它不是那么简单,因为它已经被优化了,但是你所有的“Angular 代码”都在这个$digest循环中执行。许多指令可能会使用scope.$apply(...)调用一个$digest。这将导致监视器注意到任何值的更改并执行其操作。
那么对于您最初的问题。它是否是反模式?如果您知道如何使用它,绝对不是反模式。虽然我只会使用ng-model,因为它已经有1.2.10+个版本的聪明人在上面工作过......你应该通过$q、$timeout等来处理您应用程序的所有其他“响应性”。

1

我认为它们都有各自适当的位置,对我来说,很难说停止使用其中之一。

数据绑定应始终用于使数据模型与视图中的更改保持同步。我认为我们都可以同意这一点。

我认为在控制器上使用watch以基于数据更改触发某些操作是有用的。例如,观察复杂的数据模型以计算发票的累计总额。或者观察模型以将其标记为脏。

我曾经使用广播/发射/监听器来从一个作用域向另一个作用域发送消息或指示某些更改,这可能相距几层。我创建了一个自定义指令,在控制器中使用广播事件作为钩子来执行某些操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接