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

Where Should Model State Be Stored In Angular.js

本文关键字:存储 在哪里 状态 模型 Angular js      更新时间:2023-09-26

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

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

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

然而,我并没有真正看到这个模式在我所看到的例子中使用,但我也没有看到我认为可扩展的替代方案。Angular似乎通过强制使用单例来暗中阻止使用服务作为模型(我知道有一些方法可以绕过这个问题,但它们似乎没有被广泛使用或批准)。

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

这个问题的第二个答案很有趣,也很接近我现在使用的。

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

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

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

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

控制器初始化$scope

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

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

原型范围

$scope从它的父级继承,一直继承到$rootScope,所以你可以把你的对象存储在层次结构中任何有意义的地方。您将得到一个$scope对象树,它大致与您当前的DOM相关。如果DOM发生了变化,则会根据需要为您创建新的$scope对象。

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

通过这种方式,Angular消除了我们在JavaScript中经常发现的"我在哪里存储我的数据"问题。这是我们从Angular中获得的真正巨大的生产力提升之一。

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

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

棱角分明的模型都很瘦

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

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

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

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

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

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

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

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

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

假设你想写一个方法来对你的模型做一些事情,例如同步它,或者验证它。在其他框架中,您可能会尝试使用一种方法来扩展您的模型。在Angular中,你更有可能编写一个服务。

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

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

服务不是模型

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

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

Angular让你可以很容易地持久化你的状态:

  1. 调用RESTful $resource
  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提供的实例来访问这些持久化信息。

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

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

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

AJAX ->自定义服务->模型作用域对象->控制器-> UI作用域对象-> DOM

或:

AJAX ->自定义服务->纯JavaScript对象->控制器-> UI作用域对象-> DOM