javascript - AngularJS:理解设计模式

标签 javascript angularjs design-patterns architecture client-side

this post 的上下文中作者:Igor Minar,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.



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

最佳答案

多亏了大量有值(value)的资源,我得到了一些在 AngularJS 应用程序中实现组件的一般建议:

Controller

  • Controller 应该只是一个 夹层在模型和 View 之间。尝试将其设为 尽可能。
  • 强烈推荐给避免业务逻辑 在 Controller 中。它应该移动到模型。
  • Controller 可以使用方法调用(当 child 想要与 parent 通信时可能)或 $emit、$broadcast 和 $on 方法与其他 Controller 通信。发出和广播的消息应保持在最低限度。
  • Controller 应 不在乎介绍 或 DOM 操作。
  • 试试 避免嵌套 Controller .在这种情况下,父 Controller 被解释为模型。而是将模型作为共享服务注入(inject)。
  • 范围 在 Controller 中应该用于 装订带 View 的模型和
    封装 查看型号 至于演示模型 设计模式。


  • 范围

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

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

    型号

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

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

    模型是单元测试的主要候选者,因为它们通常只有一个依赖项(某种形式的事件发射器,通常是 $rootScope)并且包含高度可测试的 域逻辑 .
  • 模型应被视为特定单元的实现。
    它基于单一职责原则。 Unit是一个实例,负责自己的相关逻辑范围,可以代表现实世界中的单个实体,并在编程世界中用来描述它。数据和状态 .
  • 模型应该封装应用程序的数据并提供 API
    访问和操作该数据。
  • 型号应该是 可移植 所以它可以很容易地运输到类似的
    应用程序。
  • 通过隔离模型中的单元逻辑,您可以更轻松地
    定位、更新和维护。
  • 模型可以使用通用的更通用的全局模型的方法
    对于整个应用程序。
  • 尽量避免使用依赖注入(inject)将其他模型组合到您的模型中,如果它不是真正依赖以减少组件耦合并增加单元 可测试性 可用性 .
  • 尽量避免在模型中使用事件监听器。这使得它们更难测试,并且通常会根据单一职责原则杀死模型。

  • 模型实现

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

    在 AngularJS 应用程序中这样做的方法是使用工厂服务类型来定义它。这将使我们能够非常容易地定义私有(private)属性和方法,并在一个地方返回可公开访问的属性和方法,这将使开发人员真正可读。

    一个例子 :
    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
      };
    });
    

    创建新实例

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

    完成同样事情的更好方法是使用工厂作为 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
        // ...
      };
    });
    

    全局模式

    一般来说,尽量避免这种情况并正确设计你的模型,这样它就可以被注入(inject) Controller 并在你的 View 中使用。

    在特定情况下,某些方法需要应用程序内的全局可访问性。
    为了使其成为可能,您可以在 $rootScope 中定义“common”属性并在应用程序引导期间将其绑定(bind)到 commonModel:
    angular.module('app', ['app.common'])
    .config(...)
    .run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
      $rootScope.common = 'commonModel';
    }]);
    

    您所有的全局方法都将存在于“公共(public)”属性中。这是某种 命名空间 .

    但是不要直接在 $rootScope 中定义任何方法。这可能导致 unexpected behavior当在您的 View 范围内与 ngModel 指令一起使用时,通常会乱扔您的范围并导致范围方法覆盖问题。

    资源

    资源让你与不同的人互动 数据源 .

    应该使用 实现单一职责原则 .

    在特殊情况下,它是 可重复使用 代理到 HTTP/JSON 端点。

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

    资源实现

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

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

    服务

    模型和资源都是服务 .

    服务未关联,松散耦合 独立的功能单元。

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

    Angular 应用程序中的服务是使用依赖注入(inject)连接在一起的可替换对象。

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

    尝试考虑 main principles of service architecture在您的应用程序中。

    一般根据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.



    客户端结构

    一般来说,应用程序的客户端分为 模块 .每个模块应该是可测试 作为一个单位。

    尝试根据 定义模块特性/功能 查看 ,而不是按类型。
    Misko’s presentation详情。

    模块组件通常可以按类型分组,例如 Controller 、模型、 View 、过滤器、指令等。

    但模块本身仍然是可重复使用 , 可转让可测试 .

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

    请引用Code Organization in Large AngularJS and JavaScript Applications详情。

    文件夹结构示例 :
    |-- 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-app 实现了 Angular 应用程序结构的一个很好的例子 - https://github.com/angular-app/angular-app/tree/master/client/src

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

    关于javascript - AngularJS:理解设计模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20286917/

    相关文章:

    javascript - 将 JSON 转换为数组以在 C3 中绘制时间序列的更好方法?

    javascript - Electron 加载 index.html 但不加载其余的包文件

    javascript - 给ngbind添加三个点

    javascript - 掩码未应用于 ng-model 的输入

    oop - 动画与MVC原理

    java - 生成器模式和继承

    javascript - 海量数据实时可视化

    javascript - 无法将 iframe 元素从指令传递给 Angular Controller

    javascript - MEAN stack Angular html <img> 标签注入(inject)不起作用

    c# - 枚举和继承