72

我发现 Angular 对模型的使用令人困惑。Angular 似乎采取了一种方法,即模型可以是您喜欢的任何东西——IE Angular 不包含显式模型类,您可以使用原生 JavaScript 对象作为模型。

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

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

但是,我还没有真正看到在我看过的示例中使用了这种模式,但我也没有看到我认为可扩展的替代方案。Angular 似乎通过强制使用单例来隐含地不鼓励使用服务作为模型(我知道有一些方法可以解决这个问题,但它们似乎没有被广泛使用或批准)。

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

[编辑]这个问题的第二个答案很有趣,与我目前使用的很接近。

4

3 回答 3

29

状态(和模型)存储在 $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,因此您可以将对象存储在层次结构中有意义的任何位置。你会得到一棵与你当前的 DOM 大致相关的 $scope 对象树。如果您的 DOM 发生更改,则会根据需要为您创建新的 $scope 对象。

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

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

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

$scope 会在模板的正确部分自动提供给您。

Angular 模型很薄

来自我们强调胖模型和瘦控制器的 Rails 背景,我发现 Angular 的“几乎没有”模型令人惊讶。事实上,在模型中放置大量业务逻辑通常会导致问题,正如我们有时在 Rails 中看到的那样,如果您不小心,它会不断增长,直到变得无法维护。

角度模型只是一个 JavaScript 对象或原语。

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

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

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

前端的模型不是真实模型

当然,您持有的客户模型不是真正的模型。您的实际模型,您的单一事实来源存在于服务器上。我们使用 API 对其进行同步,但如果两者之间存在冲突,则数据库中的模型显然是最终的胜利者。

这为您提供了折扣代码等内容的隐私。您在前端找到的模型是真实模型的公共属性的同步版本,它是远程的。

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

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

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

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

服务不是模型

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

于 2014-05-01T13:44:45.957 回答
7

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

Angular 让你可以很容易地使用以下方法来持久化你的状态:

  1. 调用 RESTful $resource
  2. 代表模型实例的 URL

在您的简单示例中,用户操作(例如selectedGallery和)的存储selectedPhoto可以使用 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.

于 2013-05-18T10:34:13.940 回答
1

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

如果您想要 Angular 范围更改通知 ( $watch) 的优点,可以使用范围对象来存储模型数据 ( var myScope = $rootScope.$new())。只是不要使用与您的 UI 绑定的相同范围对象。

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

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

或这个:

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

于 2013-05-17T20:30:10.333 回答