我发现Angular对模型的使用令人困惑。 Angular似乎采取的方式是模型可以是你喜欢的任何东西--I.E。 Angular不包含显式模型类,您可以使用vanilla JavaScript对象作为模型。
在我见过的几乎所有Angular示例中,模型实际上都是一个对象,可以手工创建,也可以通过资源从API调用返回。因为我所看到的几乎每个Angular示例都很简单,通常存储在控制器中$ scope上的模型数据以及与模型相关的任何状态(例如选择)也存储在控制器中的$ scope中。这适用于简单的应用程序/示例,但是当应用程序变得更加复杂时,这似乎过于简单了。例如,存储在控制器中的模型状态存在变为上下文的风险并且如果上下文改变则丢失。存储selectedGallery
和selectedPhoto
的控制器只能存储全局selectedImage
,而不能存储每个图库的selectedPhoto
。在这种情况下,每个图库使用一个控制器可能会否定这个问题,但从UI的角度来看似乎是浪费的,可能是不合适的和不必要的。
Angular对模型的定义似乎更接近我所认为的VO / DTO,它是服务器和客户端之间传递的愚蠢对象。我的直觉是将这样的对象包装在我认为的模型中 - 一个维护与DTO / VO相关的状态的类(例如选择),根据需要提供操纵DTO / VO的变换器,并通知其余的应用对基础数据的更改。显然,最后一部分很好地由Angular的绑定处理,但我仍然看到前两个职责的强大用例。
但是我没有真正看到我看过的例子中使用的这种模式,但我也没有看到我认为可扩展的替代方案。 Angular似乎暗中不鼓励使用服务作为模型,通过强制执行Singletons(我知道有办法解决这个问题,但它们似乎没有得到广泛使用或批准)。
那么我应该如何保持模型数据的状态?
[编辑] this question中的第二个答案很有趣,接近我目前正在使用的内容。
答案 0 :(得分:28)
$ scope是Angular的数据存储对象。它类似于数据库。 $ scope本身不是模型,但您可以在$ scope中存储模型。
每个$ scope都有一个父$ scope,一直到$ rootScope形成一个松散镜像你的DOM的树结构。当您调用需要新$ scope的指令(例如ng-controller)时,将创建一个新的$ 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获得的巨大生产力增益之一的来源。
获得全局数据(例如userId)?将它存储在$ rootScope上。获得了本地数据(例如,图库中有多个图库实例的currentImage)?将其存储在属于该库的$ scope对象上。
$ scope可在模板的正确部分自动使用。
来自Rails背景,我们强调胖模型和瘦调控制器,我发现Angular的'几乎没有'模型令人惊讶。实际上,在模型中加入大量的业务逻辑通常会导致问题,正如我们有时会看到Rails中的用户模型,如果你不小心,它会增长直到它变得不可维护。
任何对象都可以是模型。模型通常在控制器中使用JSON定义,或者从服务器使用AJAXed定义。模型可能是JSON对象,也可能只是字符串,数组甚至是数字。
当然,如果你愿意,没有什么可以阻止你向你的模型中添加额外的函数并将它们存储在JSON对象中,但是这将是一个不适合Angular的范例。
Angular对象通常是数据的存储库,而不是函数。
当然,您在客户端上持有的模型不是真正的模型。您的实际模型,您的唯一真相来源存在于服务器上。我们使用API同步它,但如果两者之间存在冲突,那么数据库中的模型显然是最终的胜利者。
这为您提供折扣代码等内容的隐私。您在前端找到的模型是真实模型的公共属性的同步版本,它是远程的。
假设你想编写一个方法来对你的模型做一些事情,同步它或者验证它。在其他框架中,您可能会尝试使用方法扩展模型。在Angular中,您更有可能编写服务。
服务是单件对象。与任何其他JavaScript对象一样,您可以在其中放置函数或数据。 Angular附带了一堆内置服务,比如$ http。您可以构建自己的,并使用依赖注入自动将它们提供给您的控制器。
服务可能包含与RESTful API通信的方法,例如,验证数据或您可能需要执行的任何其他工作。
当然,您不应将服务用作模型。将它们用作可以做东西的对象。有时他们会对你的模特做些什么。这是一种不同的思维方式,但却是可行的方式。
答案 1 :(得分:7)
首先,让我们不要忘记Angular是一个基于Web的框架,如果你只是将你的状态“保持在一个对象中”,那么用户在浏览器上点击刷新就无法生存。因此,弄清楚如何在基于Web的应用程序中保持模型数据的状态意味着要弄清楚如何持久化它,以便您的代码在浏览器环境中运行。
Angular让您可以轻松地使用以下方式保持您的状态:
在您的简单示例中,存储用户操作(例如selectedGallery
和selectedPhoto
)可以使用以下内容表示:
// List of galleries
.../gallery
// List of photos in a gallery
.../gallery/23
// A specific photo
.../gallery/23/photo/2
网址至关重要,因为它允许您的用户使用back
和forward
按钮浏览浏览器历史记录。如果您希望与应用程序的其他部分共享此状态,Web应用程序将为您使用cookie / localStorage,隐藏的框架/字段或甚至将其存储在您的服务器中提供丰富的方法。
一旦您定义了如何保持应用程序的不同状态的策略,就应该更容易决定是否希望使用.service
提供的单个对象或.factory
提供的实例来访问这些持久性信息1}}。
答案 2 :(得分:1)
Angular对如何存储您所谓的“模型对象”没有意见。 Angular控制器$scope
仅作为“视图模型”存在,用于管理UI。我建议在代码中分离这两个概念。
如果您想要Angular范围更改通知($watch
)的精确度,您可以使用范围对象来存储您的模型数据(var myScope = $rootScope.$new()
)。只是不要使用绑定UI的相同范围对象。
我建议为此编写自定义服务。所以数据流如下:
AJAX - >定制服务 - >模型范围对象 - >控制器 - > UI范围对象 - > DOM
或者这个:
AJAX - >定制服务 - >普通的旧JavaScript对象 - >控制器 - > UI范围对象 - > DOM