MVC in Angular

  • 本文主要内容是古早图书 Pro Angualr 9 的笔记,不同框架不同项目有不同理念和做事方法。
  • Angular 新的版本迭代也在尽量削减很多概念以降低入门成本,理解其当时设计意图是个学习的过程。

关于MVC

应用 MVC 模式的关键在于实现 关注点分离 这一重要前提,即应用程序中的数据模型与业务和表现逻辑分离。 在客户端网络开发中,这意味着将数据 对数据进行操作的逻辑用于显示数据的 HTML 元素 分离开来。这样,客户端应用程序就更易于开发、维护和测试。

MVC 模式在服务器端开发的传统阐述中,modelcontrollerview 是其基本构成, 我们期望从数据库中获取 model,controller 为来自浏览器的 HTTP 请求提供服务。 Angular 存在于浏览器中因而它的 MVC 主题有所变化,MVC 模式的客户端实现从服务器获取数据, 控制器和视图的目标是对模型中的数据进行操作,以控制 DOM 渲染从而创建和管理用户可以与之交互的 HTML 元素。

模式与模式狂热者

  • 模式是灵活的工具,而不是固定的规则,但并非所有开发人员都了解其中的区别,有些人甚至成为模式狂热者。这些人花在谈论模式上的时间比应用在项目上的时间还多,他们认为任何偏离自己对模式的理解的行为都是严重的犯罪。我的建议是不要理睬这种人,因为任何形式的接触都会让你生不如死,而且你也永远无法改变他们的想法。相反,你只需继续工作,并通过实际应用和交付来展示如何灵活运用模式来产生良好的结果。
  • 考虑到这一点,本书的示例中遵循了 MVC 模式的广泛概念,但对该模式进行了调整,以展示不同的功能和技术 - 接受模式中能提供价值的部分,搁置不能提供价值的部分

理解 Model

模型(MVC 中的 M)包含用户使用的数据。模型有两大类:视图模型领域模型,前者只表示从组件传递到模板的数据,后者包含业务领域中的数据,以及创建、存储和操作这些数据的操作、转换和规则,统称为模型逻辑

许多刚接触 MVC 模式的开发人员对在数据模型中包含逻辑的想法感到困惑,认为 MVC 模式的目标是将数据与逻辑分开。这是一种误解:MVC 框架的目标是将应用程序划分为三个功能区,每个功能区都可能包含逻辑和数据。我们的目标并不是从模型中消除逻辑。相反,它是为了确保模型只包含用于创建和管理模型数据的逻辑

Model 应该:

  • 包含领域数据
  • 包含创建、管理和修改域数据的逻辑(即使这意味着要通过网络服务执行远程逻辑)
  • 提供简洁的应用程序接口,公开模型数据和对其进行的操作

Model 不应该:

  • 暴露如何获取或管理模型数据的细节(换句话说,数据存储机制或远程网络服务的细节不应暴露给控制器和视图)
  • 包含根据用户交互转换模型的逻辑(因为这是组件的工作)
  • 包含向用户显示数据的逻辑(这是模板的工作)

确保模型与控制器和视图隔离的好处是,您可以更轻松地测试逻辑,而且整个应用程序的增强/维护也会变得更简单、更容易。

最好的领域模型包含获取和持久化存储数据的逻辑,并包含创建、读取、更新和删除操作的逻辑(统称为 CRUD),或者包含查询和修改数据的独立模型(称为命令和查询责任分离(CQRS)模式)。

这可能意味着模型直接包含逻辑,但更常见的情况是,模型将包含调用 RESTful 网络服务以调用服务器端数据库操作的逻辑。

理解 Controllers/Components

控制器(在 Angular 中称为组件)是 Angular Web 应用程序中的连接组织,它们是数据模型和视图之间的通道。组件添加了呈现模型的各个方面并对其执行操作所需的业务领域逻辑

Components 应该:

  • 包含设置模板初始状态所需的逻辑
  • 包含模板所需的逻辑/行为,以呈现模型中的数据
  • 包含根据用户交互更新模型所需的逻辑/行为

Components 不应该:

  • 包含操作 DOM 的逻辑(这是模板的职责)
  • 包含管理数据持久性的逻辑(这是模型的工作)

理解 Views/Templates

视图(在 Angular 中称为模板)是使用 HTML 元素定义的,这些元素通过数据绑定得到了增强。正是数据绑定让 Angular 变得如此灵活,它们将 HTML 元素转化为动态 Web 应用程序的基础。

Templates 应该:

  • 包含向用户展示数据所需的逻辑和标记

Templates 不应该:

  • 包含复杂逻辑(这最好放在组件或其他 Angular 构建模块中,如指令、服务或管道)
  • 包含创建、存储或操作领域模型的逻辑

模板可以包含逻辑,但应简单明了,并尽量少用。在模板中除了最简单的方法调用或表达式外,其他任何内容都会增加整个应用程序的测试和维护难度。

常见的设计陷阱

将逻辑放错地方

最常见的问题是将逻辑放入错误的组件中,从而破坏了 MVC 关注点分离。 以下是此问题的三种最常见类型:

  • 将业务逻辑放在模板中,而不是组件中
  • 将领域逻辑放入组件中,而不是模型中
  • 使用 RESTful 服务时将数据存储逻辑放入客户端模型中

这些都是棘手的问题,因为它们需要一段时间才能表现为问题。 应用程序仍然运行,但随着时间的推移,增强和维护将变得更加困难。 对于第三种情况,只有当数据存储发生更改时,问题才会变得明显(这种情况很少发生,直到项目成熟并且已经超出其最初的用户预测)。

随着您在 Angular 开发中获得更多经验,知道在哪里放置逻辑将成为第二天性,但以下是三个规则:

  • 模板逻辑应该只用于准备显示的数据,而不应修改模型
  • 组件逻辑永远不应该直接从模型中创建、更新或删除数据
  • 模板和组件永远不应该直接访问数据存储

当开发团队构建依赖于服务器端数据存储的怪癖的应用程序时,就会出现下一个问题。 在一个设计良好的从 RESTful 服务获取数据的 Angular 应用程序中,服务器的工作是隐藏数据存储实现细节,并以有利于客户端简单性的合适数据格式向客户端提供数据。 例如,确定客户端需要如何表示日期,然后确保在数据存储中使用该格式,如果数据存储本身无法支持该格式,则服务器将负责执行转换。

model, dataSource, repository 和 service

  • model 可以是一个领域模型 或一个 视图模型
  • 数据源(dataSource)负责向应用程序提供所需的数据,但对该数据的访问通常由存储库(repository)介导,存储库负责将该数据分发到各个应用程序构建块。
  • service 是一个广义的概念,它包括应用所需的任何值、函数或特性。狭义的服务是一个明确定义了用途的类, 它应该做一些具体的事,并做好。

通常来说,应该按照项目实际情况(一般是可测试性)灵活采用上述概念分层,让项目更简洁一些。如在 ng 官方 demo 里:

  • 将上述 model视图相关 的逻辑,直接集成在 *.component.ts 文件里
  • 讲上述 dataSource,respoitory 以及 model领域数据操作 等相关部分集中放在 *.services.ts

spring 中的 dataSource 只是连接一个数据库的基本配置,数据操作交给 repositoryservice 处理。但 repository 在前端能做的事情很少,一般会跟 service 合并。 可见 ng 中的 mvc 不像 RoRASP.NET 中那么规整清晰,model管理和修改领域数据 的职能,通常由 service 代替。

service 和 component

Angular 把组件和服务区分开,以提高模块性和复用性。

理想情况下,组件的工作只管用户体验,而不用顾及其它。它应该提供用于数据绑定的属性和方法,以便作为视图和应用逻辑的中介者。视图就是模板所渲染的东西,而程序逻辑就是用于承载模型概念的东西。

组件应该使用服务来完成那些不涉及视图或应用逻辑的任务。服务很擅长诸如从服务器获取数据、验证用户输入或直接把日志写入控制台之类的任务。通过把各种处理任务定义到可注入的服务类中,你可以让它被任何组件使用。通过在不同的环境中注入同一种服务的不同提供者,你还可以让你的应用更具适应性。