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

本文是小编为大家收集整理的关于AngularJS:了解设计模式的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

igor Minar撰写的这篇文章 p>

MVC VS MVVM与MVP .许多开发人员多么有争议的话题 可以花费数小时的时间进行辩论和争论.

几年 客户端变体),但随着时间的流逝和多亏了许多重构 和API改进,现在更接近 mvvm - $ scope 对象 可以被视为 ViewModel 正在装饰 我们称为控制器的功能.

能够对框架进行分类并将其放入MV*存储桶之一中具有一些优势. 它可以通过制造它来帮助开发人员对其API感到更加自在 更容易创建代表应用程序的心理模型 正在使用框架建造.它也可以帮助建立 开发人员使用的术语.

说,我宁愿看到开发人员构建了踢屁股应用程序 精心设计并遵循关注点的分离,而不是看到他们浪费 时间争论MV*废话.因此,我特此声明 angularjs be mvw框架 - 模型视图 - 何处.在哪里 代表" 对您有用的".

Angular为您提供了很大的灵活性来很好地分开演示 业务逻辑和演示状态的逻辑.请使用燃料 您的生产力和应用可维护性,而不是加热 关于一天结束的事情的讨论无关紧要 很多.

在客户端应用程序中实施AngularJS MVW(模型视图)设计模式是否有任何建议或准则?

推荐答案

多亏了大量有价值的资源,我对Angularjs应用中实施组件的一般建议:


控制器

  • 控制器应该只是模型和视图之间的中间>.尝试使其成为薄.

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

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

  • 控制器应该不关心演示或DOM操纵.

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

  • 范围在控制器中应使用绑定模型,并具有视图和
    封装查看模型对于演示模型设计模式.


范围

将范围视为在模板中仅读取和在控制器中仅写入.范围的目的是指模型,而不是模型.

进行双向绑定(NG模型)时,请确保您不会直接与范围属性结合.


模型

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

.

模型提供了分离数据和显示的绝佳方法.

模型是用于单位测试的主要候选者,因为它们通常具有一种依赖性(某种形式的事件发射极,通常为 $ rootscope ),并且包含高度可测试的域逻辑.

  • 应将模型视为特定单元的实现. 它基于单一责任性基本.单位是一个实例,负责其自己的相关逻辑范围,它可能代表现实世界中的单一实体,并以数据和状态.

  • 来描述它
  • 模型应封装应用程序的数据并提供 api 访问和操纵该数据.

  • 模型应为便携式,因此可以轻松地将其运输到类似 应用程序.

  • 通过在模型中隔离单元逻辑,您使您更容易 找到,更新和维护.

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

  • ,如果不依赖于减少组件耦合并增加单位可可可用性可用性,请尝试使用依赖注入将其他模型组合到您的模型中.

  • 尝试避免在模型中使用事件侦听器.它使他们更难测试并通常以单责任性原理杀死模型.

模型实施

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

在AngularJS应用程序中执行此操作的方法是使用 Factory 服务类型来定义它.这将使我们能够非常简单地定义私人属性和方法,并在单个位置返回公开访问的属性,这将使它真正可读.

示例:

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
  };
});

创建新实例

尝试避免拥有一个返回新功能的工厂,因为这开始分解依赖注入,并且图书馆的行为会尴尬,尤其是对于第三方.

完成同一件事的更好方法是将工厂用作API,返回带有Getter和setter方法的对象集合.

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
    // ...
  };
});

全局模型

通常,

尝试避免这种情况并正确设计模型,因此可以将其注入控制器并在您的视图中使用.

特别是某些方法需要应用程序中的全局可访问性. 为了使您可以在 $ rootscope 中定义" common "属性,并将其绑定到应用程序bootstrap期间 commonmodel :

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

您所有的全局方法都将生活在" common "属性中.这是某种名称空间.

,但不要直接在 $ rootscope 中定义任何方法.这可能会导致意外行为与您的视图范围内与NGModel指令一起使用时问题.


资源

资源使您可以与不同的数据源进行交互.

应使用单责任性基本.

实施

特别的情况下,它是可重复使用的代理http/json端点.

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

资源实施

创建资源对象的工厂,可让您与RESTFUL服务器端数据源进行交互.

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


服务

模型和资源都是服务.

服务是无关的,松散耦合是独立的功能单位.

服务是Angular从服务器端带到客户端Web应用程序的功能,该服务通常使用了很长时间.

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

Angular带有不同类型的服务.每个都有自己的用例.请阅读了解服务类型有关详细信息.

尝试考虑服务体系结构的主要原理在您的应用程序中. p>

一般而言,根据 Web Services Services glosospary :

服务是一种抽象资源,代表 执行从而形成连贯功能的任务 提供者实体和请求者实体的视图.要使用,一个 服务必须由混凝土提供商代理实现.


客户端结构

在一般客户端,应用程序被拆分为模块.每个模块应作为一个单位可测试.

尝试根据功能/功能或 View 来定义模块,而不是类型. 请参阅 misko的演示有关详细信息.

.

模块组件通常按照控制器,模型,视图,过滤器,指令等类型进行分组.

但模块本身仍然保持可重复使用,可转移和可测试.

.

开发人员更容易找到代码的某些部分及其所有依赖项.

请参阅代码组织大型Angularjs和Javascript应用程序有关详细信息.

构造文件夹的示例:

|-- 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

Angular应用结构的好示例由 angular-app - https://github.com/angular-app/angular-app/tree/master/master/client/src

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

其他推荐答案

我相信,如您所提供的报价所示,Igor对此的看法只是一个更大问题的冰山一角.

MVC 及其导数(MVP,PM,MVVM)在单个代理中都很好,而dandy都很好,但是服务器 - 客户端体系结构对所有目的都是两个代理系统,人们是通常如此沉迷于这些模式,以至于他们忘记了手头的问题要复杂得多.通过试图遵守这些原则,它们实际上是有缺陷的架构.

让我们一点一点.

指南

视图

在角上下文中,视图是DOM.准则是:

做:

  • 当前范围变量(仅读).
  • 致电控制器进行操作.

不要:

  • 放任何逻辑.

作为诱人,短而无害的外观:

ng-click="collapsed = !collapsed"

它几乎表示任何开发人员现在都可以了解系统的工作方式,以检查JavaScript文件和HTML文件.

控制器

做:

  • 通过将数据放在范围上.
  • 将视图绑定到"模型".
  • 响应用户操作.
  • 处理演示逻辑.

不要:

  • 处理任何业务逻辑.

最后一个准则的原因是控制器是观点而非实体的姐妹.它们也不可重复使用.

您可以说指令是可重复使用的,但指令也是视图(dom)的姐妹 - 他们从不打算与实体通话.

当然,有时视图代表实体,但这是一个相当具体的情况.

换句话说,控制器应专注于演示 - 如果您投入业务逻辑,不仅您可能最终会遇到一个夸大的,少见的控制器,而且还违反了关注的分离原理.

因此,Angular中的控制器实际上是 >或 mvvm .

等等,如果控制器不应该处理业务逻辑,应该谁?

什么是模型?

您的客户端模型通常是部分而陈旧

除非您正在编写脱机Web应用程序或非常简单的应用程序(几个实体),否则您的客户端模型很可能是:

  • 部分
    • 它没有所有实体(例如分页的情况)
    • 否则它没有所有数据(例如分页的情况)
  • 陈旧 - 如果系统有多个用户,则在任何时候您都无法确定客户端的模型与服务器所持的模型相同.

真正的模型必须坚持

在传统的MCV中,该模型是持续存在的唯一东西.每当我们谈论模型时,都必须在某个时候坚持使用这些模型.您的客户可能会随意操纵模型,但是直到服务器的往返完成,工作才完成.

后果

上面的两个点应注意 - 您的客户所拥有的模型只能涉及部分简单的业务逻辑.

因此,使用小写M可能是明智的 - 因此,它确实是 mvc , mvp 和 mvvm .大M是为服务器的.

业务逻辑

也许关于业务模型的最重要概念之一是您可以将它们细分为2种类型(我省略了第三张 viev-business ,因为这是另一天的故事):

  • 域逻辑 - aka 企业业务规则,独立于应用程序的逻辑.例如,给出具有firstName和sirName属性的模型,可以将像getFullName()之类的getter视为无关.
  • 应用程序逻辑 - aka 应用程序业务规则,特定于应用程序.例如,错误检查和处理.

重要的是要强调,在客户端上下文中这两个都是不是"真实"的业务逻辑 - 它们仅处理对客户很重要的部分.应用程序逻辑(不是域逻辑)应负责促进与服务器和大多数用户交互的通信;域逻辑在很大程度上是小规模,实体特定和呈现驱动的.

问题仍然存在 - 您将它们扔到Angular应用程序中?

3 vs 4层体系结构

所有这些MVW框架都使用3层:

三个圆.
<p>但是,在客户方面有两个基本问题:</p>
<ul>
<li>该模型是部分的,陈旧的,不会持续.</li>
<li>没有放置应用程序逻辑的地方.</li>
</ul>
<p>此策略的一种替代方法是<a href= 4图层策略:

从内部到外部 - 企业业务规则,应用程序业务规则,接口适配器,框架和驱动程序

这里真正的交易是应用程序业务规则层(用例),这通常是对客户的不对劲.

这层是由互动者(鲍勃叔叔)实现的,这几乎是马丁·福勒(Martin Fowler)所说的操作脚本服务层.

具体示例

考虑以下Web应用程序:

  • 该应用程序显示了一个分页的用户列表.
  • 用户单击"添加用户".
  • 模型打开了一个表格以填写用户详细信息.
  • 用户填写表格并点击提交.

现在应该发生一些事情:

  • 该表格应为客户端验证.
  • 应将请求发送到服务器.
  • 如果有一个.
  • ,应处理错误
  • 用户列表可能(由于分页)可能需要更新.

我们在哪里扔所有这些?

如果您的体系结构涉及调用$resource的控制器,则所有这些都将在控制器中发生.但是有一个更好的策略.

提出的解决方案

以下图显示了如何通过在Angular客户端添加另一个应用程序逻辑层来解决上述问题:

 4个盒子 -  DOM指向控制器,该指向应用程序逻辑,指向$ resource to $ resource <

因此,我们在控制器之间添加一个层到$资源,此层(让我们称其为 Interactor ):

  • 是服务.对于用户,可以称为UserInteractor.
  • 它提供了与用例相对应的方法,封装应用程序逻辑.
  • .
  • 它控制对服务器的请求.该图层不是使用自由形式参数调用$资源的控制器,而是确保对服务器返回数据的请求.
  • 它用域逻辑原型装饰返回的数据结构.

等等,上面的具体示例的要求:

  • 用户单击"添加用户".
  • 控制器向交互式询问空白的用户模型,以业务逻辑方法进行装饰,例如validate()
  • 提交时,控制器调用模型validate()方法.
  • 如果失败,控制器会处理错误.
  • 如果成功,控制器将使用createUser()
  • 调用交互式
  • 交互式调用$ Resource
  • 响应后,交互式将任何错误委派给了控制器.
  • 成功响应后,交互式确保如果需要,用户列表更新.

其他推荐答案

与Artem答案中的出色建议相比,一个小问题,但是就代码可读性而言,我最好发现完全定义对象内的API,以最大程度地减少代码中的来回浏览以查看每个变量的定义:

angular.module('myModule', [])
// or .constant instead of .value
.value('myConfig', {
  var1: value1,
  var2: value2
  ...
})
.factory('myFactory', function(myConfig) {
  ...preliminary work with myConfig...
  return {
    // comments
    myAPIproperty1: ...,
    ...
    myAPImethod1: function(arg1, ...) {
    ...
    }
  }
});

如果return对象看起来"太拥挤",那是该服务做得太多的迹象.

本文地址:https://www.itbaoku.cn/post/627552.html

问题描述

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

其他推荐答案

I believe Igor's take on this, as seen in the quote you have provided, is just the iceberg tip of a far greater problem.

MVC and its derivatives (MVP, PM, MVVM) are all good and dandy within a single agent, but a server-client architecture is for all purposes a two-agent system, and people are often so obsessed with these patterns that they forget that the problem at hand is far more complex. By trying to adhere to these principles they actually end up with a flawed architecture.

Let's do this bit by bit.

The guidelines

Views

Within Angular context, the view is the DOM. The guidelines are:

Do:

  • Present scope variable (read only).
  • Call the controller for actions.

Don't:

  • Put any logic.

As tempting, short, and harmless this looks:

ng-click="collapsed = !collapsed"

It pretty much signify any developer that now to understand how the system work they need to inspect both the Javascript files, and the HTML ones.

Controllers

Do:

  • Bind the view to the 'model' by placing data on the scope.
  • Respond to user actions.
  • Deal with presentation logic.

Don't:

  • Deal with any business logic.

The reason for the last guideline is that controllers are sisters to views, not entities; nor they are reusable.

You could argue that directives are reusable, but directives too are sisters to views (DOM) - they were never intended to correspond to entities.

Sure, sometimes views represent entities, but that's a rather specific case.

In other words, controllers shall focus on presentation - if you throw business logic in, not only you are likely to end up with an inflated, little-manageable controller, but you also violate the separation of concern principle.

As such, controllers in Angular are really more of Presentation Model or MVVM.

And so, if controllers shouldn't deal with business logic, who should?

What is a model?

Your client model is often partial and stale

Unless you are writing an offline web application, or an application that is terribly simple (few entities), you client model is highly likely to be:

  • Partial
    • Either it doesn't have all entities (like in the case of pagination)
    • Or it doesn't have all the data (like in the case of pagination)
  • Stale - If the system has more than one user, at any point you can't be sure that the model the client holds is the same as the one the server hold.

The real model must persist

In traditional MCV, the model is the only thing being persisted. Whenever we talk about models, these must be persisted at some point. Your client may manipulate models at will, but until the roundtrip to the server was completed successfully, the job ain't done.

Consequences

The two points above should serve as a caution - the model your client holds can only involve a partial, mostly simple business logic.

As such, it is perhaps wise, within client context, to use lowercase M - so it's really mVC, mVP, and mVVm. The big M is for the server.

Business logic

Perhaps one of the most important concepts about business models is that you can subdivide them to 2 types (I omit the third view-business one as that's a story for another day):

  • Domain logic - aka Enterprise business rules, the logic that is application-independent. For example, give a model with firstName and sirName properties, a getter like getFullName() can be considered application-independent.
  • Application logic - aka Application business rules, which is application specific. For instance, error checks and handling.

It is important to stress that both of these within a client context are not 'real' business logic - they only deal with the portion of it that is important for the client. Application logic (not domain logic) should have the responsibility of facilitating communication with the server and most user interaction; while the domain logic is largely small-scale, entity-specific, and presentation-driven.

The question still remains - where do you throw them within an angular application?

3 vs 4 layer architecture

All these MVW frameworks use 3 layers:

Three circles. Inner - model, middle - controller, outer - view

But there are two fundamental issues with this when it comes to clients:

  • The model is partial, stale and doesn't persist.
  • No place to put application logic.

An alternative to this strategy is the 4 layer strategy:

4 circles, from inner to outer - Enterprise business rules, Application business rules, Interface adapters, Frameworks and drivers

The real deal here is the application business rules layer (Use cases), which often goes amiss on clients.

This layer is realised by interactors (Uncle Bob), which is pretty much what Martin Fowler calls an operation script service layer.

Concrete example

Consider the following web application:

  • The application shows a paginated list of users.
  • The user clicks 'Add user'.
  • A model opens with a form to fill user details.
  • The user fills the form and hit submit.

A few things should happen now:

  • The form should be client-validated.
  • A request shall be sent to the server.
  • An error shall be handled, if there is one.
  • The user list may or may not (due to pagination) needs updating.

Where do we throw all of this?

If your architecture involves a controller that calls $resource, all of this will happen within the controller. But there is a better strategy.

A proposed solution

The following diagram shows how the problem above can be solve by adding another application logic layer in Angular clients:

4 boxes - DOM points to Controller, which points to Application logic, which points to $resource

So we add a layer between controller to $resource, this layer (lets call it interactor):

  • Is a service. In the case of users, it may be called UserInteractor.
  • It provide methods corresponding to use cases, encapsulating application logic.
  • It controls the requests made to the server. Instead of a controller calling $resource with free-form parameters, this layer ensure that requests made to the server return data on which domain logic can act.
  • It decorates the returned data structure with domain logic prototype.

And so, with the requirements of the concrete example above:

  • The user clicks 'Add user'.
  • The controller asks the interactor for a blank user model, the is decorated with business logic method, like validate()
  • Upon submission, the controller calls the model validate() method.
  • If failed, the controller handles the error.
  • If successful, the controller calls the interactor with createUser()
  • The interactor calls $resource
  • Upon response, the interactor delegates any errors to the controller, which handles them.
  • Upon successful response, the interactor ensures that if needed, the user list updates.

其他推荐答案

A minor issue comparing to the great advices in Artem's answer, but in terms of code readability, I found best to define the API completely inside the return object, to minimize going back and forth in code to look wheverer variables are defined:

angular.module('myModule', [])
// or .constant instead of .value
.value('myConfig', {
  var1: value1,
  var2: value2
  ...
})
.factory('myFactory', function(myConfig) {
  ...preliminary work with myConfig...
  return {
    // comments
    myAPIproperty1: ...,
    ...
    myAPImethod1: function(arg1, ...) {
    ...
    }
  }
});

If the return object becomes looking "too crowded", that is a sign that the Service is doing too much.