模型状态应存储在Angular.js中的何处


72

我发现Angular对模型的使用令人困惑。Angular似乎采用了模型可以随心所欲的方法-IE Angular不包括显式的模型类,您可以将原始JavaScript对象用作模型。

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

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

但是,我在所查看的示例中并未真正看到过这种模式,但是我也没有看到我认为可伸缩的替代方案。Angular似乎通过强制使用Singletons隐式地不鼓励将Services用作模型(我知道有解决此问题的方法,但它们似乎并未得到广泛使用或认可)。

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

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


您不喜欢每种型号类型的一项服务吗?阿galleryService可以存储画廊的阵列。
Mark Rajcok

@MarkRajcok我对Singleton Services没有任何问题。在许多情况下,它们仅是您所需要的,并且在您描述的情况下,它们会很好地工作。但是,如果每个画廊都有一系列照片,每个照片都需要保持状态,该怎么办?
分心

3
我想我可能在这里过分简化和设计了……我将拥有三个模型对象:1)照片对象; 2)画廊对象(其中一个属性是照片对象的数组); 3 )galleryCollection对象(其中一个属性是Gallery对象的数组)。(galleryCollection可能不是一个单独的对象,它可能只是galleryService本身的一部分。)方法和属性可以同时存在于这三个对象上。在我看来,每张照片和画廊都是一个单独的对象,它们只是由服务进行分组/管理/访问。可以在服务之外定义模型。
Mark Rajcok

1
我同意@MarkRajcok(通常是这样)。最干净,最简单的方法是使用他描述的服务。这极大地简化了测试,并使每个服务更具可扩展性和可重用性。我认为将服务视为返回模型对象而不是返回模型API至关重要。该API是您在控制器中用来访问一个或一组模型对象的API。因此,gallery服务可以具有熟悉的方法(获取,更新,删除等),同时在内部管理状态并使用单独的记录方法(例如)返回对象$resource
乔什·大卫·米勒

5
对于其他任何想知道的人:VO =值对象,DTO =数据传输对象
Tamlyn

Answers:


28

状态(和模型)存储在$ 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定义的,或者是通过服务器AJAX输入的。模型可以是JSON对象,也可以只是字符串,数组甚至是数字。

当然,没有什么可以阻止您向模型中添加其他功能并将它们存储在JSON对象中的,但是,这将移植到与Angular并不完全匹配的范例中。

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

前端的模型不是真实模型

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

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

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

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

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

服务可能包含与RESTful API进行通信或验证数据或可能需要执行的其他任何工作的方法。

服务不是模型

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


3
谢谢。所有的优点。但是,您没有解决问题的症结。您是否建议将所有状态都存储在服务器上的“真实”模型中?因为这实际上抵消了富客户端的主要好处之一-速度。我认为,在很多情况下,客户端模型与服务器端模型一样“真实”,尽管它显然取决于体系结构。在我的问题中,我专门针对仅与客户端相关的状态-选择状态-而不是需要保留至服务器的更永久的状态。
分心

2
是的,您当然是对的,状态应保留在客户端上,前端任务应在客户端中进行。我只是觉得讨论这个问题很有用。
超级照明2014年

1
谈论它绝对好。我认为这仍然是一个未解决的问题。状态是怪物潜伏的地方,没有人喜欢对付它。
分心

2
当然,Angular使用$ scope为我们解决了State问题。
superluminary 2014年

好的帖子,这是我尝试在我的应用程序中遵循的范例。
mjj1409

7

首先,别忘了Angular是基于Web的框架,如果仅在对象中“保持状态”,它将无法在用户单击浏览器刷新时幸免。因此,弄清楚如何在基于Web的应用程序中保持Model数据的状态意味着弄清楚如何持久化它,以便代码在浏览器环境中起作用。

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,隐藏的框架/字段,甚至将其存储在服务器中。

一旦定义了如何持久化应用程序不同状态的策略,就可以更轻松地决定是否要使用提供的singleton对象.service或通过实例提供访问这些持久化的信息.factory


3
这很有意义,我完全同意需要通过路径/查询字符串来跟踪应用程序状态,但是可以肯定的是,这是应用程序状态的一种投影,而不是一种存储状态的方式。URL栏是模型和视图的奇怪组合,但最终反映了内部应用程序状态或提示此状态发生更改。其余应用程序本身仍然需要内部跟踪此状态的方法,并且在Service / Model上执行此操作,应用程序的参与者可以共享访问权限,这对我来说似乎是正确的解决方案。
分心

而且.service和.factory都返回Singleton Services(这很违反直觉)。
分心

4
@marcoseu,很有趣……尽管您可以做到,但是应该吗?现在,依赖注入与该工厂的工作方式非常不同-即,由于返回的是函数(对象)而不是对象(即{}),因此必须使用它new来创建实例。这意味着您无法使用普通的依赖项注入语法与其他实体共享该实例。我认为这会使其他可能使用您的代码的Angular开发人员感到困惑。
Mark Rajcok

1
@MarkRajcok我有意返回一个new'able函数,以便new关键字将指示该服务不与其他实体共享。例如,我在ui.bootstrap.alert周围有一个包装,我在控制器中实例化了传递$ scope。以后需要显示警报时,只需致电alert.success("Ok")。话虽如此,我确实同意不清楚调用者何时应使用new,但确实要由服务的用户来了解应如何使用该服务。
marcoseu

1
@MarkRajcok我确实意识到的使用new会造成混淆,但它提醒我正在调用的服务不可共享。但是,如果只在语法方面考虑问题,那么您也可以返回一个对象,即:在中调用new .factory。我更新了插件,以说明如何做到这一点。
marcoseu

1

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

如果您希望获得Angular范围更改通知($watch),可以根据需要使用范围对象存储模型数据(var myScope = $rootScope.$new())。只是不要使用与UI绑定在一起的作用域对象。

为此,我建议编写自定义服务。因此,数据流如下所示:

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

或这个:

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


3
我已经分离了这些概念,这正是我问这个问题的原因。处理模型状态的标准Angular方法似乎是存储在此View Model中,这正是让我感到不舒服的原因。创建一个新的作用域对我来说并不是一个好的解决方案。感觉就像是滥用范围。
分心
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.