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

我发现Angular的模型使用混乱。 Angular似乎采取了一种模式可以是任何你喜欢的方式 – IE Angular不包括一个明确的模型类,你可以使用vanilla JavaScript对象作为模型。

在我看到的几乎每个Angular示例中,模型实际上都是一个对象,无论是由手工创build的,还是通过资源的API调用返回的。 因为几乎所有我看过的Angular示例都很简单,通常在$ scope中存储在控制器中的模型数据以及与模型相关的任何状态(例如select)也存储在控制器的$ scope中。 这对于简单的应用程序/示例来说工作得很好,但是当应用程序变得更加复杂时,这看起来很简单。 例如,存储在控制器中的模型状态有可能变为上下文,并且如果上下文改变则丢失。 存储selectedGalleryselectedPhoto控制器只能存储全局selectedImage ,而不是每个图库的selectedPhoto 。 在这种情况下,为每个图库使用一个控制器可能会否定这个问题,但是从UI的angular度来看,这样做看起来很浪费,可能是不适当和不必要的。

Angular对模型的定义似乎更接近于我认为VO / DTO是在服务器和客户端之间传递的哑对象。 我的本能就是将这样一个对象包裹在我认为是一个模型的类中 – 一个维护与DTO / VO相关的状态的类(比如select),根据需要提供增变器来操纵DTO / VO,并通知剩下的应用对底层数据的修改。 很明显,Angular的绑定很好地处理了最后一部分,但是我仍然看到前两个职责的强大用例。

然而,我并没有真正看到这个模式在我看过的例子中使用,但我也没有看到我会考虑一个可扩展的替代scheme。 Angular似乎暗中鼓励使用服务作为模型通过强制单身人士(我知道有办法解决这个问题,但似乎并没有广泛使用或批准)。

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

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

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

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

每个$ scope都有一个父$范围,一直到$ rootScope形成一个松散地反映你的DOM的树结构。 当你调用一个需要一个新的$ scope的指令时,例如ng-controller,一个新的$ scope对象将被创build并被添加到树中。

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

控制器初始化$范围

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

在图片库的情况下,您将拥有一个imageGallery控制器,然后使用ng-controller指令将这些控制器应用到您希望成为图库的DOM的每个部分。 页面的这一部分将获得它自己的$ scope,您将使用它来存储selectedPhoto属性。

原型范围

$ scope从它的父项inheritance,使用普通的旧原型inheritance一​​直到$ rootScope,所以你可以把你的对象存储在层次结构的任何地方。 您会得到一个与您当前的DOM大致相关的$ scope对象树。 如果你的DOM改变了,你可以根据需要为你创build新的$ scope对象。

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

通过这种方式,Angular不再使用我们经常在JavaScript中find的旧的“我在哪里存储数据”问题。 这是我们从Angular获得的巨大生产力收益之一的来源。

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

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

angular模型很薄

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

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

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

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

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

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

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

这样可以为您提供诸如折扣代码等隐私服务。您在前端find的模型是真实模型的公共属性的同步版本,这是远程的。

业务逻辑可以生活在服务中。

假设你想写一个方法来对你的模型做些什么,同步它或者validation它。 在其他框架中,您可能会试图用一种方法来扩展您的模型。 在Angular中,你会更有可能写一个服务。

服务是单例对象。 像其他任何JavaScript对象一样,您可以将函数或数据放入其中。 Angular带有一些内置的服务,比如$ http。 你可以build立你自己的,并使用dependency injection来自动提供给你的控制器。

例如,服务可能包含与RESTful API交谈的方法,或者validation您的数据或您可能需要执行的其他工作。

服务不是模型

当然,你不应该使用服务作为模型。 使用它们作为可以做东西的对象。 有时他们会对你的模型做些什么。 这是一种不同的思维方式,而是一种可行的方式。

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

Angular使得你可以很容易地坚持你的状态:

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

在你的简单例子中,像selectedGalleryselectedPhoto这样的用户操作的存储可以使用类似于URL的URL来表示:

 // List of galleries .../gallery // List of photos in a gallery .../gallery/23 // A specific photo .../gallery/23/photo/2 

该URL非常重要,因为它允许用户使用backforwardbutton浏览浏览器历史logging。 如果你想与你的应用程序的其他部分共享这个状态,Web应用程序提供了丰富的方法来完成使用cookie / localStorage,隐藏的框架/字段,甚至存储在你的服务器。

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

Angular对你如何存储你所谓的“模型对象”没有意见。 angular度控制器$scope只是作为一个“视图模型”来pipe理你的UI。 我build议在代码中分离这两个概念。

如果你想要angular度范围变化通知( $watch )的好,你可以使用一个范围对象来存储你的模型数据( var myScope = $rootScope.$new() )。 只是不要使用你的UI绑定到的相同的范围对象。

我build议为此编写自定义服务。 所以数据stream如下所示:

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

或这个:

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