AngularJS:理解设计模式 [英] AngularJS: Understanding design pattern

查看:24
本文介绍了AngularJS:理解设计模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这篇博文的上下文中,AngularJS 的负责人 Igor Minar:><块引用>

MVC vs MVVM vs MVP.很多开发者都在争论的话题可以花费数小时的时间进行辩论和争论.

几年来,AngularJS 更接近于 MVC(或者更确切地说是它的一种客户端变体),但随着时间的推移和许多重构和 api 改进,现在更接近 MVVM - $scope 对象可以被认为是被装饰的 ViewModel我们称之为控制器的函数.

能够对框架进行分类并将其放入 MV* 存储桶之一具有一些优势.它可以帮助开发人员更舒适地使用它的 apis更容易创建代表应用程序的心智模型正在使用该框架构建.它还可以帮助建立开发人员使用的术语.

话虽如此,我宁愿看到开发人员构建精心设计并遵循关注点分离,而不是将它们视为浪费是时候争论 MV* 废话了.为此,我特此声明AngularJS 成为 MVW 框架 - Model-View-Whatever.无论在哪里代表一切对你有用".

Angular 为您提供了很大的灵活性来很好地分离演示文稿来自业务逻辑和表示状态的逻辑.请用它燃料您的生产力和应用程序可维护性而不是加热讨论在一天结束时无关紧要的事情很多.

对于在客户端应用程序中实现 AngularJS MVW (Model-View-Whatever) 设计模式,是否有任何建议或指南?

解决方案

感谢大量有价值的资源,我得到了一些在 AngularJS 应用程序中实现组件的一般性建议:

<小时>

控制器

  • 控制器应该只是模型和视图之间的中间层.尽量让它.

  • 强烈建议在控制器中避免业务逻辑.它应该移到模型中.

  • Controller 可以使用方法调用(当孩子想要与父母通信时可能)或 $emit$broadcast$ 与其他控制器通信 方法.发出和广播的消息应保持在最低限度.

  • 控制器应该不关心表现或DOM操作.

  • 尽量避免嵌套控制器.在这种情况下,父控制器被解释为模型.而是将模型作为共享服务注入.

  • 控制器中的
  • 范围应该用于绑定模型与视图和
    封装视图模型作为表示模型设计模式.

<小时>

范围

将范围视为模板中的只读控制器中的只写.范围的目的是指模型,而不是模型.

在进行双向绑定 (ng-model) 时,请确保不要直接绑定到作用域属性.

<小时>

型号

AngularJS 中的模型是由服务定义的单例.

模型提供了一种很好的方式来分离数据和显示.

模型是单元测试的主要候选者,因为它们通常只有一个依赖项(某种形式的事件发射器,通常是 $rootScope)并且包含高度可测试的域逻辑.

  • 模型应被视为特定单元的实现.它基于单一职责原则.单元是一个实例,负责它自己的相关逻辑范围,可以在现实世界中表示单个实体,并在编程世界中用数据和状态来描述它.

  • 模型应封装应用程序的数据并提供API访问和操作该数据.

  • 模型应该便携,以便可以很容易地运输到类似的应用程序.

  • 通过隔离模型中的单元逻辑,您可以更轻松地定位、更新和维护.

  • 模型可以使用通用的更通用的全局模型的方法整个应用程序.

  • 如果不是真正依赖的话,尽量避免使用依赖注入将其他模型组合到您的模型中,以减少组件耦合并增加单元可测试性可用性.

  • 尽量避免在模型中使用事件监听器.这使得它们更难测试,并且通常会根据单一职责原则杀死模型.

模型实现

由于模型应该在数据和状态方面封装一些逻辑,因此它应该在架构上限制对其成员的访问,从而我们可以保证松散耦合.

在 AngularJS 应用程序中实现它的方法是使用 factory 服务类型来定义它.这将使我们能够非常轻松地定义私有属性和方法,并且还可以在一个地方返回可公开访问的属性和方法,这将使开发人员真正可读.

示例:

angular.module('search').factory('searchModel', ['searchResource', function (searchResource) {var itemsPerPage = 10,当前页 = 1,总页数 = 0,allLoaded = 假,搜索查询;函数初始化(参数){itemsPerPage = params.itemsPerPage ||每页项目;searchQuery = params.substring ||搜索查询;}函数 findItems(page, queryParams) {searchQuery = queryParams.substring ||搜索查询;返回 searchResource.fetch(searchQuery, page, itemsPerPage).then( function (results) {totalPages = results.totalPages;currentPage = results.currentPage;allLoaded = totalPages <= currentPage;返回结果列表});}函数 findNext() {返回 findItems(currentPage + 1);}函数 isAllLoaded() {返回所有已加载;}//返回公共模型 API返回 {/*** @param {Object} 参数*/初始化:初始化,/*** @param {Number} 页* @param {Object} queryParams* @return {Object} 承诺*/查找:查找项目,/*** @return {布尔}*/allLoaded: isAllLoaded,/*** @return {Object} 承诺*/查找下一个:查找下一个};});

创建新实例

尽量避免工厂返回一个新的有能力的函数,因为这会开始破坏依赖注入,库的行为会很尴尬,特别是对于第三方.

完成同样事情的更好方法是使用工厂作为 API 来返回一个对象集合,并附有 getter 和 setter 方法.

angular.module('car').factory('carModel', ['carResource', function (carResource) {功能汽车(数据){angular.extend(this, data);}汽车原型 = {保存:函数(){//TODO: 去除不相关的字段var carData =//...返回 carResource.save(carData);}};函数 getCarById ( id ) {返回 carResource.getById(id).then(function (data) {返回新车(数据);});}//公共 API返回 {//...findById: getCarById//...};});

全球模型

通常尽量避免这种情况并正确设计您的模型,以便将其注入控制器并在您的视图中使用.

在特殊情况下,某些方法需要在应用程序中具有全局可访问性.为了使之成为可能,您可以在 $rootScope 中定义common"属性,并在应用程序引导期间将其绑定到 commonModel:

angular.module('app', ['app.common']).config(...).run(['$rootScope', 'commonModel', 函数 ($rootScope, commonModel) {$rootScope.common = 'commonModel';}]);

您的所有全局方法都将位于common"属性中.这是某种命名空间.

但不要直接在您的 $rootScope 中定义任何方法.当在您的视图范围内与 ngModel 指令一起使用时,这可能会导致意外行为,通常会乱扔您的范围并导致范围方法覆盖问题.

<小时>

资源

Resource 可让您与不同的数据源进行交互.

应使用单一责任原则来实施.

在特殊情况下,它是 HTTP/JSON 端点的可重用代理.

资源被注入模型并提供发送/检索数据的可能性.

资源实现

创建资源对象的工厂,允许您与 RESTful 服务器端数据源进行交互.

返回的资源对象具有提供高级行为的操作方法,无需与低级 $http 服务交互.

<小时>

服务

模型和资源都是服务.

服务是独立的、松散耦合的功能单元.

服务是 Angular 从服务器端为客户端 Web 应用程序带来的一项功能,服务端已经普遍使用了很长时间.

Angular 应用中的服务是使用依赖注入连接在一起的可替换对象.

Angular 提供了不同类型的服务.每个都有自己的用例.请阅读了解服务类型了解详情.

尝试在您的应用程序中考虑服务架构的主要原则.>

一般来说,根据网络服务术语表:

<块引用>

服务是一种抽象资源,代表一种能力执行从以下方面形成连贯功能的任务提供者实体和请求者实体的视图.要使用,一个服务必须由具体的提供者代理实现.

<小时>

客户端结构

一般来说,应用程序的客户端被分成模块.每个模块都应该作为一个单元可测试.

尝试根据特性/功能视图来定义模块,而不是根据类型.有关详细信息,请参阅 Misko 的演示文稿.

模块组件通常可以按类型分组,例如控制器、模型、视图、过滤器、指令等.

但模块本身仍然可重用可转移可测试.

开发人员可以更轻松地找到代码的某些部分及其所有依赖项.

请参阅大型 AngularJS 和 JavaScript 应用程序中的代码组织 了解详情.

文件夹结构示例:

|-- src/||-- 应用程序/|||-- app.js|||-- 家/||||-- home.js||||-- homeCtrl.js||||-- home.spec.js||||-- home.tpl.html||||-- home.less|||-- 用户/||||-- 用户.js||||-- userCtrl.js||||-- userModel.js||||-- userResource.js||||-- user.spec.js||||-- 用户.tpl.html||||-- user.less||||-- 创建/|||||-- 创建.js|||||-- 创建Ctrl.js|||||-- 创建.tpl.html||-- 普通/|||-- 认证/||||-- 身份验证.js||||-- authenticationModel.js||||-- authenticationService.js||-- 资产/|||-- 图片/||||-- 标志.png||||-- 用户/|||||-- 用户图标.png|||||-- 用户默认头像.png||-- index.html

Angular 应用程序结构的好例子由 angular-app 实现 - https://github.com/angular-app/angular-app/tree/master/client/src

现代应用程序生成器也考虑了这一点 - https://github.com/yeoman/generator-angular/issues/109

In the context of this post by Igor Minar, lead of AngularJS:

MVC vs MVVM vs MVP. What a controversial topic that many developers can spend hours and hours debating and arguing about.

For several years AngularJS was closer to MVC (or rather one of its client-side variants), but over time and thanks to many refactorings and api improvements, it's now closer to MVVM – the $scope object could be considered the ViewModel that is being decorated by a function that we call a Controller.

Being able to categorize a framework and put it into one of the MV* buckets has some advantages. It can help developers get more comfortable with its apis by making it easier to create a mental model that represents the application that is being built with the framework. It can also help to establish terminology that is used by developers.

Having said, I'd rather see developers build kick-ass apps that are well-designed and follow separation of concerns, than see them waste time arguing about MV* nonsense. And for this reason, I hereby declare AngularJS to be MVW framework - Model-View-Whatever. Where Whatever stands for "whatever works for you".

Angular gives you a lot of flexibility to nicely separate presentation logic from business logic and presentation state. Please use it fuel your productivity and application maintainability rather than heated discussions about things that at the end of the day don't matter that much.

Are there any recommendations or guidelines for implementing AngularJS MVW (Model-View-Whatever) design pattern in client-side applications?

解决方案

Thanks to a huge amount of valuable sources I've got some general recommendations for implementing components in AngularJS apps:


Controller

  • Controller should be just an interlayer between model and view. Try to make it as thin as possible.

  • It is highly recommended to avoid business logic in controller. It should be moved to model.

  • Controller may communicate with other controllers using method invocation (possible when children wants to communicate with parent) or $emit, $broadcast and $on methods. The emitted and broadcasted messages should be kept to a minimum.

  • Controller should not care about presentation or DOM manipulation.

  • Try to avoid nested controllers. In this case parent controller is interpreted as model. Inject models as shared services instead.

  • Scope in controller should be used for binding model with view and
    encapsulating View Model as for Presentation Model design pattern.


Scope

Treat scope as read-only in templates and write-only in controllers. The purpose of the scope is to refer to model, not to be the model.

When doing bidirectional binding (ng-model) make sure you don't bind directly to the scope properties.


Model

Model in AngularJS is a singleton defined by service.

Model provides an excellent way to separate data and display.

Models are prime candidates for unit testing, as they typically have exactly one dependency (some form of event emitter, in common case the $rootScope) and contain highly testable domain logic.

  • Model should be considered as an implementation of particular unit. It is based on single-responsibility-principle. Unit is an instance that is responsible for its own scope of related logic that may represent single entity in real world and describe it in programming world in terms of data and state.

  • Model should encapsulate your application’s data and provide an API to access and manipulate that data.

  • Model should be portable so it can be easily transported to similar application.

  • By isolating unit logic in your model you have made it easier to locate, update, and maintain.

  • Model can use methods of more general global models that are common for the whole application.

  • Try to avoid composition of other models into your model using dependency injection if it is not really dependent to decrease components coupling and increase unit testability and usability.

  • Try to avoid using event listeners in models. It makes them harder to test and generally kills models in terms of single-responsibility-principle.

Model Implementation

As model should encapsulate some logic in terms of data and state, it should architecturally restrict access to its members thus we can guarantee loose coupling.

The way to do it in AngularJS application is to define it using factory service type. This will allow us to define private properties and methods very easy and also return publically accessible ones in single place that will make it really readable for developer.

An example:

angular.module('search')
.factory( 'searchModel', ['searchResource', function (searchResource) {

  var itemsPerPage = 10,
  currentPage = 1,
  totalPages = 0,
  allLoaded = false,
  searchQuery;

  function init(params) {
    itemsPerPage = params.itemsPerPage || itemsPerPage;
    searchQuery = params.substring || searchQuery;
  }

  function findItems(page, queryParams) {
    searchQuery = queryParams.substring || searchQuery;

    return searchResource.fetch(searchQuery, page, itemsPerPage).then( function (results) {
      totalPages = results.totalPages;
      currentPage = results.currentPage;
      allLoaded = totalPages <= currentPage;

      return results.list
    });
  }

  function findNext() {
    return findItems(currentPage + 1);
  }

  function isAllLoaded() {
    return allLoaded;
  }

  // return public model API  
  return {
    /**
     * @param {Object} params
     */
    init: init,

    /**
     * @param {Number} page
     * @param {Object} queryParams
     * @return {Object} promise
     */
    find: findItems,

    /**
     * @return {Boolean}
     */
    allLoaded: isAllLoaded,

    /**
     * @return {Object} promise
     */
    findNext: findNext
  };
});

Creating new instances

Try to avoid having a factory that returns a new able function as this begins to break down dependency injection and the library will behave awkwardly, especially for third parties.

A better way to accomplish the same thing is to use the factory as an API to return a collection of objects with getter and setter methods attached to them.

angular.module('car')
 .factory( 'carModel', ['carResource', function (carResource) {

  function Car(data) {
    angular.extend(this, data);
  }

  Car.prototype = {
    save: function () {
      // TODO: strip irrelevant fields
      var carData = //...
      return carResource.save(carData);
    }
  };

  function getCarById ( id ) {
    return carResource.getById(id).then(function (data) {
      return new Car(data);
    });
  }

  // the public API
  return {
    // ...
    findById: getCarById
    // ...
  };
});

Global Model

In general try to avoid such situations and design your models properly thus it can be injected into controller and used in your view.

In particular case some methods require global accessibility within application. To make it possible you can define ‘common’ property in $rootScope and bind it to commonModel during application bootstrap:

angular.module('app', ['app.common'])
.config(...)
.run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
  $rootScope.common = 'commonModel';
}]);

All your global methods will live within ‘common’ property. This is some kind of namespace.

But do not define any methods directly in your $rootScope. This can lead to unexpected behavior when used with ngModel directive within your view scope, generally littering your scope and leads to scope methods overriding issues.


Resource

Resource lets you interact with different data sources.

Should be implemented using single-responsibility-principle.

In particular case it is a reusable proxy to HTTP/JSON endpoints.

Resources are injected in models and provide possibility to send/retrieve data.

Resource implementation

A factory which creates a resource object that lets you interact with RESTful server-side data sources.

The returned resource object has action methods which provide high-level behaviors without the need to interact with the low level $http service.


Services

Both model and resource are services.

Services are unassociated, loosely coupled units of functionality that are self-contained.

Services are a feature that Angular brings to client-side web apps from the server side, where services have been commonly used for a long time.

Services in Angular apps are substitutable objects that are wired together using dependency injection.

Angular comes with different types of services. Each one with its own use cases. Please read Understanding Service Types for details.

Try to consider main principles of service architecture in your application.

In general according to Web Services Glossary:

A service is an abstract resource that represents a capability of performing tasks that form a coherent functionality from the point of view of providers entities and requesters entities. To be used, a service must be realized by a concrete provider agent.


Client-side structure

In general client side of the application is splitted into modules. Each module should be testable as a unit.

Try to define modules depending on feature/functionality or view, not by type. See Misko’s presentation for details.

Module components may be conventionally grouped by types such as controllers, models, views, filters, directives etc.

But module itself remains reusable, transferable and testable.

It is also much easier for developers to find some parts of code and all its dependencies.

Please refer to Code Organization in Large AngularJS and JavaScript Applications for details.

An example of folders structuring:

|-- src/
|   |-- app/
|   |   |-- app.js
|   |   |-- home/
|   |   |   |-- home.js
|   |   |   |-- homeCtrl.js
|   |   |   |-- home.spec.js
|   |   |   |-- home.tpl.html
|   |   |   |-- home.less
|   |   |-- user/
|   |   |   |-- user.js
|   |   |   |-- userCtrl.js
|   |   |   |-- userModel.js
|   |   |   |-- userResource.js
|   |   |   |-- user.spec.js
|   |   |   |-- user.tpl.html
|   |   |   |-- user.less
|   |   |   |-- create/
|   |   |   |   |-- create.js
|   |   |   |   |-- createCtrl.js
|   |   |   |   |-- create.tpl.html
|   |-- common/
|   |   |-- authentication/
|   |   |   |-- authentication.js
|   |   |   |-- authenticationModel.js
|   |   |   |-- authenticationService.js
|   |-- assets/
|   |   |-- images/
|   |   |   |-- logo.png
|   |   |   |-- user/
|   |   |   |   |-- user-icon.png
|   |   |   |   |-- user-default-avatar.png
|   |-- index.html

Good example of angular application structuring is implemented by angular-app - https://github.com/angular-app/angular-app/tree/master/client/src

This is also considered by modern application generators - https://github.com/yeoman/generator-angular/issues/109

这篇关于AngularJS:理解设计模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆