在 Angular.js 中模型状态应该存储在哪里

我发现 Angular 对模型的使用令人困惑。Angular 似乎采取了这样一种方法: 模型可以是任何你喜欢的东西——例如,Angular 不包含显式的模型类,你可以使用普通的 JavaScript 对象作为模型。

在我看到的几乎每个 Angular 示例中,模型实际上都是一个对象,要么是手工创建的,要么是通过 Resource 从 API 调用返回的。因为我研究的几乎每个 Angular 示例都很简单,通常模型数据存储在控制器中的 $scope 上,与模型相关的任何状态(例如选择)也存储在控制器中的 $scope 上。这对于简单的应用程序/示例来说很好用,但是当应用程序变得更加复杂时,这似乎过于简单化了。例如,如果上下文发生变化,存储在控制器中的模型状态有可能变成上下文状态并丢失; 存储 selectedGalleryselectedPhoto的控制器只能存储全局 selectedImage,而不是每个画廊一个 selectedPhoto。在这种情况下,每个画廊使用一个控制器可能会解决这个问题,但是从 UI 的角度来看,这似乎是浪费的,可能是不合适的,也是不必要的。

Angular 对模型的定义似乎更接近于我认为的 VO/DTO,它是在服务器和客户机之间传递的哑对象。我的直觉是将这样一个对象包装在我认为的 Model 中——一个类,它维护与 DTO/VO 相关的状态(比如选择) ,根据需要提供修改器来操作 DTO/VO,并通知其余应用程序对底层数据的更改。显然,Angular 的绑定很好地处理了最后一部分,但是我仍然看到了前两个职责的强大用例。

然而,我还没有真正在我所看到的示例中看到这种模式的使用,但是我也没有看到我认为可伸缩的替代方案。Angular 似乎通过强制使用 Singleton 来隐含地阻止使用 Services 作为模型(我知道有办法绕过这一点,但它们似乎没有得到广泛使用或认可)。

那么我应该如何保持模型数据的状态呢?

[编辑] 这个问题中的第二个答案很有趣,和我现在使用的答案很接近。

22338 次浏览

对于如何存储所谓的“模型对象”,Angular 没有任何意见。角控制器 $scope仅作为一个“视图模型”存在,用于管理 UI。我建议在代码中将这两个概念分开。

如果您想要角度范围更改通知的准确性($watch) ,您可以使用范围对象来存储您的模型数据,如果您愿意(var myScope = $rootScope.$new())。只是不要使用与 UI 绑定到的同一个作用域对象。

我建议为此编写自定义服务,因此数据流是这样的:

AJAX —— > 自定义服务—— > 模型范围对象—— > 控制器—— > UI 范围对象—— > DOM

或者这样:

AJAX —— > 自定义服务—— > 普通的 JavaScript 对象—— > 控制器—— > UI 范围对象—— > DOM

首先,我们不要忘记,Angular 是一个基于 web 的框架,如果你“保持你的状态”只是在一个对象,它将不会生存用户点击刷新他们的浏览器。因此,弄清楚如何在基于 Web 的应用程序中保持 Model 数据的状态意味着弄清楚如何保持它,以便代码能够在浏览器环境中运行。

Angular 可以让你很容易地保持你的状态,使用:

  1. 对 RESTful $资源的调用
  2. 表示模型实例的 URL

在您的简单示例中,用户操作(如 selectedGalleryselectedPhoto)的存储可以使用 URL 表示,如:

// List of galleries
.../gallery


// List of photos in a gallery
.../gallery/23


// A specific photo
.../gallery/23/photo/2

URL 非常重要,因为它允许用户使用 backforward按钮浏览浏览器历史记录。如果你希望与应用程序的其他部分共享这种状态,web 应用程序提供了丰富的方法,可以通过使用 cookie/localStorage、隐藏框架/字段,甚至将其存储在服务器中来实现。

一旦定义了如何持久化应用程序的不同状态的策略,决定是使用 .service提供的单例对象访问这些持久化信息还是通过 .factory访问实例应该会更容易。

状态(和模型)存储在 $scope 中

$scope 是 Angular 的数据存储对象。类似于数据库。$scope 本身不是模型,但是可以将模型存储在 $scope 中。

每个 $scope 都有一个父 $scope,一直到 $rootScope 形成松散地镜像 DOM 的树结构。当您调用需要新的 $scope (如 ng-controller)的指令时,将创建一个新的 $scope 对象并将其添加到树中。

$scope 对象使用原型继承连接。这意味着,如果您在树中的较高级别添加模型,则所有较低级别都可以使用该模型。这是一个非常强大的特性,它使得 $scope 层次结构对于模板作者来说几乎是透明的。

控制器初始化 $scope

控制器的作用是初始化 $scope 。同一个控制器可以在页面的不同部分初始化许多 $scope 对象。控制器被实例化,设置 $scope 对象,然后退出。您可以使用相同的控制器在页面的不同部分初始化许多 $scope。

对于您的图像库,您将拥有一个 imageGallery 控制器,然后您可以使用 ng-controller 指令将其应用到您希望成为图库的 DOM 的每个部分。页面的这一部分将获得自己的 $scope,用于存储 selectedPhoto 属性。

原型望远镜

$scope 使用普通的原型继承从其父继承到 $rootScope,因此您可以将对象存储在层次结构中任何有意义的地方。您得到了一个 $scope 对象树,它与您当前的 DOM 大致相关。如果 DOM 发生更改,将根据需要为您创建新的 $scope 对象。

$scope 只是一个普通的 JavaScript 对象。创建多个 $scope 对象并不比创建一个包含多个 currentImage 对象的数组更浪费。这是组织代码的明智方式。

通过这种方式,Angular 解决了我们在 JavaScript 中经常遇到的“数据存储在哪里”的老问题。这是我们从 Angular 中获得的最大的生产力收益之一。

获得全局数据(例如 userId)了吗?存储在 $rootScope 上。获得本地数据(例如,有多个画廊实例的画廊中的 currentImage) ?将其存储在属于该库的 $scope 对象上。

$scope 在模板的正确部分自动可用。

角度模型很薄

在 Rails 的背景下,我们强调胖模型和瘦控制器,我发现 Angular 的“几乎不存在”模型令人惊讶。事实上,在模型中加入大量的业务逻辑常常会导致问题的产生,就像我们有时在 Rails 中看到的 User 模型一样,如果不小心的话,它会不断增长,直到变得不可维护。

角度模型只是一个 JavaScript 对象或基元。

任何对象都可以是模型。通常在控制器中使用 JSON 定义模型,或者在服务器中使用 AJAXed 定义模型。模型可能是一个 JSON 对象,也可能只是一个字符串、数组,甚至是一个数字。

当然,如果您愿意的话,没有什么可以阻止您向模型中添加额外的函数并将它们存储在 JSON 对象中,但是这将是在一个并不真正适合 Angular 的范例中进行移植。

角对象通常是数据的存储库,而不是函数。

前端的模型不是真正的模型

当然,客户端上的模型并不是真正的模型。你的真实模型,你唯一的真相来源就在服务器上。我们使用一个 API 来同步它,但是如果两者之间存在冲突,那么数据库中的模型显然是最终的胜利者。

这样可以给你一些隐私,比如折扣代码等等。您在前端找到的模型是实际模型的公共属性的同步版本,它是远程的。

业务逻辑可以存在于服务中。

例如,您想编写一个方法来对模型进行某些操作、同步它或验证它。在其他框架中,您可能会尝试用一种方法来扩展您的模型。在 Angular 中,您更有可能编写服务。

服务是单例对象。像任何其他 JavaScript 对象一样,您可以将函数或数据放入其中。Angular 提供了一系列内置服务,比如 $http。你可以构建自己的依赖注入,并使用它们自动提供给你的控制器。

例如,服务可能包含与 RESTful API 对话的方法,或者验证数据的方法,或者您可能需要做的任何其他工作。

服务不是模型

当然,您不应该将服务用作模型。把它们当做可以做事的物体。有时候他们会对你的模特做些什么。这是一种不同的思维方式,但却是可行的。