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

Und*_*ion 71 javascript state model angularjs

我发现Angular对模型的使用令人困惑.Angular似乎采用了一种模型可以是你喜欢的方法 - IE Angular不包含显式模型类,你可以使用vanilla JavaScript对象作为模型.

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

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

但是我没有真正看到我看过的例子中使用的这种模式,但我也没有看到我认为可扩展的替代方案.Angular似乎暗中不鼓励使用服务作为模型,通过强制执行Singletons(我知道有办法解决这个问题,但它们似乎没有得到广泛使用或批准).

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

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

sup*_*ary 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,因此您可以将对象存储在有意义的层次结构中的任何位置.您将获得一个与您当前DOM大致相关的$ scope对象树.如果您的DOM发生更改,则会根据需要为您创建新的$ scope对象.

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

通过这种方式,Angular取消了我们经常在JavaScript中找到的旧"我在哪里存储我的数据"问题.这是我们从Angular获得的巨大生产力增益之一的来源.

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

$ scope可以在模板的正确部分自动使用.

角度模型很薄

来自Rails背景,我们强调胖模型和瘦调控制​​器,我发现Angular的'几乎没有'模型令人惊讶.事实上,在你的模型中加入大量的业务逻辑往往会导致问题,正如我们有时会看到Rails中的用户模型,如果你不小心,它会增长直到它变得不可维护.

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

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

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

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

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

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

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

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

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

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

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

服务不是模特

当然,您不应该将服务用作模型.将它们用作可以做东西的对象.有时他们会对你的模特做些什么.这是一种不同的思维方式,但却是一种可行的方法.

  • 谢谢.所有的好处.但是,你没有解决问题的关键.您是否建议将所有状态存储在服务器上的"真实"模型中?因为这有效地否定了富客户端的一个主要好处 - 速度.我认为在许多情况下,客户端模型与服务器端模型一样"真实",尽管它显然取决于架构.在我的问题中,我专门针对仅与客户端选择状态相关的状态 - 而不是需要持久保存到服务器的更永久的状态. (3认同)
  • 是的,你当然是正确的,国家应该在客户端,并且前端任务应该在客户端进行.我只觉得谈论这个问题很有用. (2认同)
  • 对于我们来说,状态当然是由Angular使用$ scope解决的. (2认同)

mar*_*seu 7

首先,我们不要忘记Angular是一个基于Web的框架,如果你只是将你的状态"保持在一个对象中",那么用户在浏览器上刷新就无法生存.因此,弄清楚如何在基于Web的应用程序中保持模型数据的状态意味着要弄清楚如何持久化它,以便您的代码在浏览器环境中运行.

Angular使你可以很容易地使用以下方式保持你的状态:

  1. 调用RESTful $资源
  2. 表示模型实例的URL

在您的简单示例中,存储用户操作(如selectedGallery和)selectedPhoto可以使用URL表示,例如:

// List of galleries
.../gallery

// List of photos in a gallery
.../gallery/23

// A specific photo
.../gallery/23/photo/2
Run Code Online (Sandbox Code Playgroud)

该URL非常重要,因为它允许您的用户使用backforward按钮浏览浏览器历史记录.如果您希望与应用程序的其他部分共享此状态,Web应用程序将为您使用cookie/localStorage,隐藏的框架/字段或甚至将其存储在您的服务器中提供丰富的方法.

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

  • @marcoseu,有意思......虽然你可以这样做,不是吗?现在依赖注入与该工厂的工作方式完全不同 - 即,由于您返回的是函数(对象)而不是对象(即{}),因此必须使用`new`来创建实例.这意味着您无法使用常规依赖项注入语法与其他实体共享该实例.我认为这对于可能使用您的代码的其他Angular开发人员来说会让人感到困惑. (4认同)
  • 这很有意义,我完全同意应用程序状态需要通过路径/查询字符串进行跟踪,但是这肯定是应用程序状态的投影,而不是存储它的方法.URL栏是模型和视图的奇怪组合,但它最终反映内部应用程序状态或提示此状态的更改.其余应用程序本身仍然需要在内部跟踪此状态的方法,并且在服务/模型上执行此操作,应用程序的参与者可以共享访问权限,这对我来说似乎是正确的解决方案. (3认同)

djs*_*ith 1

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

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

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

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

或这个:

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

  • 我已经区分了这些概念,这正是我问这个问题的原因。处理模型状态的标准 Angular 方式似乎是存储在这个视图模型中,这正是让我感到不舒服的地方。对我来说,创建一个新的范围并不是一个好的解决方案。感觉像是滥用范围。 (3认同)