白霸天的博客


  • 首页

  • 归档

  • 标签
白霸天的博客

你需要知道的 Angular 编程指南(上)

发表于 2015-11-15 |

一个好的编程风格有助于团队的协同开发,所以在做 angular 开发时,我们也有一些约定,本文章主要是针对于使用 angular 和 coffeescript 编程的团队。(这是一个粗糙的翻译版本,原文的链接在文章下,感兴趣的同学可以去看)

Angular 编程风格指导原文地址

##1. 单一职责

原则 1:一个文件只能定义一个组件

下面的例子定义了app模块和他的依赖,把控制器和服务都定义到一个文件了

-- 不推荐 --
SomeController = ()->
someFactory = ()->
angular
    .module('app', ['ngRoute'])
    .controller('SomeController' , SomeController)
    .factory('someFactory' , someFactory)

同样的组建,现在我们把它分解到他们自己的文件中

-- 推荐方式 --
-- app.module.js --

angular
    .module('app', ['ngRoute'])


-- 推荐方式 --
-- someController.js --
SomeController = ()->
angular
    .module('app')
    .controller('SomeController' , SomeController)


-- 推荐方式 --
-- someFactory.js --
someFactory = ()->
angular
    .module('app')
    .factory('someFactory' , someFactory)

##2. 模块

原则 2:定义模块的时候不要用变量来定义,用设置的语法来定义

因为我们使用的单一职责的原则,每一个组建一个文件,在定义组建的时候你已经用angular.module来介绍这个组建是哪个模块的了。

-- 不推荐 --
app = angular.module('app', [
    'ngAnimate'
    'ngRoute'
    'app.shared'
    'app.dashboard'
])

下面是不用变量的语法

-- 推荐方式 --
angular
    .module('app', [
    'ngAnimate'
    'ngRoute'
    'app.shared'
    'app.dashboard'
])

拓展:当使用模块的时候,不要用变量,要用链式定义的语法

这样做能够使代码的可读性更高,同时也能避免变量的泄露和碰撞(重名)

-- 不推荐--
app = angular.module('app')
app.controller('SomeController' , SomeController)
SomeController = ()->

-- 推荐方式 --
SomeController = ()->

angular
  .module('app')
  .controller('SomeController' , SomeController)

设置 vs 获取所有的实例只要设置一次

一个模块只要被创建一次,之后的模块获取只要通过这个切入点来获取就可以了

  • 用 angular.module('app', []) 来设置模块
  • 用angular.module('app')来获取模块

命名函数 vs 匿名函数:使用命名函数来代替向回掉函数传递匿名函数

这样能够是代码的可读性更高,更加容易debug,减少回调函数的嵌套

-- 不推荐 --
angular
  .module('app')
  .controller('Dashboard', ()->)
  .factory('logger', ()-> )

-- 推荐方式 --
-- dashboard.js --
Dashboard = ()->
  # logic goes here -->
  return

angular
  .module('app')
  .controller('Dashboard', Dashboard)

-- 推荐方式 --
-- logger.js --
logger = ()->
  # logic goes here -->
  return

angular
  .module('app')
  .factory('logger', logger)

IIFE(立即调用函数表达式):把angular 组建包裹在能够马上调用的函数表达式中

IIFE 把变量从全局作用域里解放出来,这样做能防止把变量和函数定义在全局作用句中从而造成变量的碰撞()变量重名造成的莫名其妙的bug)

(->
  logger = ()->
    # logic goes here -->
    return

  angular
    .module('app')
    .factory('logger', logger);

)()

主意:为了使代码更加简洁,下面的编程先省略 IIFE 语法

##3. 控制器

controller as view 语法:用 controllerAs语法来代替经典的把controller 绑定到 $scope 作用域的语法

  • 控制器是一个构造类,需要通过newed来创建一个新的实例,但是controllerAS语法更像 javascript 的构造函数
  • 这样的用法能够促进我们在视图中绑定对应对象的变量( 用customer.name来取代name)等等,这样能够是我们的代码更易读,避免我们没有指定对象的时候的一些参考问题。
  • 能够让我们避免在视图中的嵌套控制器中使用$parent来调用父级控制器

    -- 不推荐 --
    <div ng-controller="Customer">
      {{ name }}
    </div>
    -- 推荐方式 --
    <div ng-controller="Customer as customer">
      {{ customer.name }}
    </div>
    

controllerAs Controller Syntax:用controllerAs来代替传统的将控制器绑定到$scope的语法

  • controllerAs通过 this来从控制器的内部把返回的内容绑定到$scope 上
    controllerAs在语法上比 $scope要友好,使用controllerAs 你依旧可以把把数据绑定到视图,依旧可以访问绑定在$scope上的方法
  • 能够避免可能把方法定义到服务上更好的时候,想要将控制器中的方法绑定到$scope上,在服务中要考虑$scope 的用法,在控制器中只有必要的时候才能绑定到$scope上,举个例子,当要通过$emit,$broadcast,$on来传递和接收事件的时候,要在服务中定义,再在控制器中调用
    主意:介于 coffeescript 会自动返回最后一行,我们最好在函数的最后一行加上一个return 声明(即使这个函数没有任何东西返回),大多数上可以没有返回声明,但是在我自己的开发过程中,没有返回声明的时候,会报错,所以建议还是加上这个返回声明

    -- 不推荐 --
    (->
      Customer = ($scope)->
        $scope.name = {}
        $scope.sendMessage = ()->
      angular
        .module('app')
        .controller('Customer', Customer)
    )()
    -- 推荐方法 --
    (->
      Customer = ()->
        @name = {}
        @sendMessage = ()->
        return
    
      angular
        .module('app')
        .controller('Customer', Customer)
    )()
    

controllerAS with vm当使用controllerAs语法时,用一个变量来代替 this ,找一个统一的变量名来代替视图模型例如 vm

this关键字是联系上下文的,当在控制器中使用函数的时候可能会改变上下文,为了避免这样的情况,最好用一个变量来捕获this

-- 不推荐 --
(->
  Customer = ()->
    @name = {}
    @sendMessage = ()->
      # here @/this is not the same
      @stuff = "stuff"

    return
  angular
    .module('app')
    .controller('Customer', Customer)
)()


-- 推荐方式 --
(->
  Customer = ()->
    vm = @
    vm.name = {}
    vm.sendMessage = ()->

    return
  angular
    .module('app')
    .controller('Customer', Customer)
)()

【tip】

### OR use the fat arrow in functions => ###
(->
  Customer = ()->
    @name = {}
    @sendMessage = ()=>
      @stuff

    return
  angular
    .module('app')
    .controller('Customer', Customer)
)()

【note】:你可以把下面这段代码放到你的代码的最前一行,来避免一些组建的语法检查

### jshint validthis: true ###
vm = @

把变量放在控制器的最前面

把数据绑定的变量成员(按照字母顺序)放在控制器的最前面,而不是遍布在整个控制器中。

把数据绑定的变量成员放在控制器的最前面,使程序更加易读,帮你立即分辨控制器中这个变量成员可以绑定在视图中

-- 不推荐 --
function SessionsController() {
    var vm = this;

    vm.gotoSession = function() {
      /* ... */
    };
    vm.refresh = function() {
      /* ... */
    };
    vm.search = function() {
      /* ... */
    };
    vm.sessions = [];
    vm.title = 'Sessions';
}

-- 推荐方式 --
function SessionsController() {
    var vm = this;

    vm.gotoSession = gotoSession;
    vm.refresh = refresh;
    vm.search = search;
    vm.sessions = [];
    vm.title = 'Sessions';

    ////////////

    function gotoSession() {
      /* */
    }

    function refresh() {
      /* */
    }

    function search() {
      /* */
    }
}

【note】 如果函数是一行的就把函数也放在控制器的前面,这样做对代码的阅读性不会有影响

-- 不推荐--
function SessionsController(data) {
    var vm = this;

    vm.gotoSession = gotoSession;
    vm.refresh = function() {
        blabla
    };
    vm.search = search;
    vm.sessions = [];
    vm.title = 'Sessions';
}



-- 推荐方法--
function SessionsController(sessionDataService) {
    var vm = this;

    vm.gotoSession = gotoSession;
    vm.refresh = sessionDataService.refresh; // 1 liner is OK
    vm.search = search;
    vm.sessions = [];
    vm.title = 'Sessions';
}

函数声明,隐藏实现细节

函数声明,隐藏实现的细节。把用于数据绑定的变量成员的声明,放在控制器的前面,实现放在文件的后面。

  • 把数据绑定的成员放在前面易于代码的阅读,能让你一眼就分辨出哪个变量用于视图的哪块区域的绑定
  • 把函数的实现放在文件的后面,把函数实现这部分比较复杂的部分放在后面。能让你直接看到函数声明这部分重要的信息
  • 因为函数的声明被提升了,所以在函数定义前,你是不需要关心这个函数的
  • 你不需要担心函数声明的位子移动的问题,你不用担心”因为a函数是依赖于b函数的,a函数移动到b函数前面会让你的代码出错”这样子的问题
  • 顺序在函数表达式中很重要

    --不建议使用函数表达式--
    function AvengersController(avengersService, logger) {
        var vm = this;
        vm.avengers = [];
        vm.title = 'Avengers';
    
        var activate = function() {
            return getAvengers().then(function() {
                logger.info('Activated Avengers View');
            });
        }
    
        var getAvengers = function() {
            return avengersService.getAvengers().then(function(data) {
                vm.avengers = data;
                return vm.avengers;
            });
        }
    
        vm.getAvengers = getAvengers;
    
        activate();
    }
    

主意上面的例子中把重要的函数声明分散在控制器中,但在下面的例子中重要的函数声明都被提升到了顶部。举个栗子,函数绑定变量如vm.avengers和vm.title,把函数实现的细节放在后面,这样更加有利于代码的阅读

--  推荐方法 --
function AvengersController(avengersService, logger) {
    var vm = this;
    vm.avengers = [];
    vm.getAvengers = getAvengers;
    vm.title = 'Avengers';

    activate();

    function activate() {
        return getAvengers().then(function() {
            logger.info('Activated Avengers View');
        });
    }

    function getAvengers() {
        return avengersService.getAvengers().then(function(data) {
            vm.avengers = data;
            return vm.avengers;
        });
    }
}

###把控制器的逻辑定义放到服务中

  • 逻辑可能会在多个控制器中复用,通过封装在服务中来通过函数暴露给控制器
  • 把逻辑封装在服务中,能更容易在单元测试中分离成独立作用域,在控制器中调用函数也更容易模拟
  • 从控制器中移除依赖,把实现细节隐藏起来
  • 让控制器变得简单,苗条,且专注

    -- 不推荐 --
    function OrderController($http, $q, config, userInfo) {
        var vm = this;
        vm.checkCredit = checkCredit;
        vm.isCreditOk;
        vm.total = 0;
    
        function checkCredit() {
            var settings = {};
            // Get the credit service base URL from config
            // Set credit service required headers
            // Prepare URL query string or data object with request data
            // Add user-identifying info so service gets the right credit limit for this user.
            // Use JSONP for this browser if it doesn't support CORS
            return $http.get(settings)
                .then(function(data) {
                 // Unpack JSON data in the response object
                   // to find maxRemainingAmount
                   vm.isCreditOk = vm.total <= maxRemainingAmount
                })
                .catch(function(error) {
                   // Interpret error
                   // Cope w/ timeout? retry? try alternate service?
                   // Re-reject with appropriate error for a user to see
                });
        };
    }
    -- 推荐方法 --
    function OrderController(creditService) {
        var vm = this;
        vm.checkCredit = checkCredit;
        vm.isCreditOk;
        vm.total = 0;
    
        function checkCredit() {
           return creditService.isOrderTotalOk(vm.total)
              .then(function(isOk) { vm.isCreditOk = isOk; })
              .catch(showError);
        };
    }
    

使控制器变得专注

为一个视图定义一个控制器,不要讲一个控制器为多个视图复用,要把复用的逻辑封装到服务中,保证一个控制器只专注于他的视图

给多个视图复用的控制器是很脆的,一个好的 E2E测试覆盖率是用来确定一个应用的稳定性的

路由分配

当一个控制器必须和某个视图绑定但也有可能和其他视图或者控制器复用,把控制器的定义和路由条状一起定义

【note】如果一个视图不是通过路由而是通过其他方式加载的,请用ng-controller="Avengers as vm"语法

在路由中分配控制器,允许不同的路由去调用不同的控制器和视图,当控制器是在视图中通过ng-controller 进行声明,那么这个视图就要和这个控制器一直关联

-- 不推荐 --

// route-config.js
angular
    .module('app')
    .config(config);

function config($routeProvider) {
    $routeProvider
        .when('/avengers', {
          templateUrl: 'avengers.html'
        });
}

<!-- avengers.html -->
<div ng-controller="AvengersController as vm">
</div>



-- 推荐方式 --

// route-config.js
angular
    .module('app')
    .config(config);

function config($routeProvider) {
    $routeProvider
        .when('/avengers', {
            templateUrl: 'avengers.html',
            controller: 'Avengers',
            controllerAs: 'vm'
        });
}
<!-- avengers.html -->
<div>
</div>
白霸天的博客

你需要知道的 Angular 编程指南(下)

发表于 2015-11-10 |

一个好的编程风格有助于团队的协同开发,所以在做 angular 开发时,我们也有一些约定,本文章主要是针对于使用 angular 和 coffeescript 编程的团队。(这是一个粗糙的翻译版本,原文的链接在文章下,感兴趣的同学可以去看)

Angular 编程引导

服务

单例

服务是通过new关键字进行实例化的,用this来定义调用公用的方法和变量,和工厂服务很相似,为了统一,可以使用工厂服务

【note】:所有的 angular 服务都是单例模式,这就意味着服务的每次注入都只有一个实例

// service
angular
    .module('app')
    .service('logger', logger);

function logger() {
  this.logError = function(msg) {
    /* */
  };
}

// factory
angular
    .module('app')
    .factory('logger', logger);

function logger() {
    return {
        logError: function(msg) {
          /* */
        }
   };
}

工厂服务

单一职责

工厂服务也应该是单一职责,当一个服务要实现的功能超过一个目的,就要重新定义一个工厂服务

单例

工厂服务是单例服务,返回的对象包括了服务的成员对象

【note】所有的 Angular 服务都是单例

可调用的成员靠前

将服务器的可调用的成员(暴露的接口)提升到服务器前面,(从《学习 javascript 设计模式》中派生出来)

  • 把可调用的变量成员放在服务的最前面,能提供你的代码可读性,能让你一眼就看出,这个服务哪些成员变量是可调用和可被测试的
  • 在文件变长的时候,这样做就显得很有必要了,你不用滚动到文件的下面去查看,这个服务到底暴露了哪些接口
  • 当你的函数超过一行代码的时候,会降低你的代码可读性,阅读时也会造成多余的滚动操作,所以你要把可调用接口的定义,和服务的return ,提升到文件的顶部定义,把实现的细节放在文件下面,这样来增加代码的可读性
      ### 不推荐方式 ###
(->
  dataService = ()->

    someValue = ''

    save = ()->
      # ... #

    validate = ()->
      # ... #

    return
      save: save,
      someValue: someValue,
      validate: validate

  angular
    .module('app')
    .service('dataService', dataService)
)()

### 推荐方式 ###
(->
  dataService = ()->

    someValue = ''

    ##########

    return
      save: ()->
       # . #

      validate: ()->
       # . #
  angular
    .module('app')
    .service('dataService', dataService)
)()

这种方法绑定的的数据是宿主对象的映射,通过模块模式暴露的单一的原始数据是不能独自进行更新的

### 不推荐方式 ###
angular
  .module('app.widgets')

  # order directive that is specific to the order module
  .directive('orderCalendarRange', orderCalendarRange)

  # sales directive that can be used anywhere across the sales app
  .directive('salesCustomerInfo', salesCustomerInfo)

  # spinner directive that can be used anywhere across apps
  .directive('sharedSpinner', sharedSpinner)

  ### implementation details ###



### 推荐方式 ###

 ###
 # @desc order directive that is specific to the order module at a company named Acme
 # @file calendarRange.directive.js
 # @example <div acme-order-calendar-range></div>
 ###
angular
  .module('sales.order')
  .directive('acmeOrderCalendarRange', orderCalendarRange)

 ###
 # @desc spinner directive that can be used anywhere across the sales app at a company named Acme
 # @file customerInfo.directive.js
 # @example <div acme-sales-customer-info></div>
 ###
angular
  .module('sales.widgets')
  .directive('acmeSalesCustomerInfo', salesCustomerInfo)

 ###
 # @desc spinner directive that can be used anywhere across apps at a company named Acme
 # @file spinner.directive.js
 # @example <div acme-shared-spinner></div>
 ###
angular
  .module('shared.widgets')
  .directive('acmeSharedSpinner', sharedSpinner)

  ### implementation details ###

指令

一个指令一个文件,把所有的指令混到一个文件中容易,但是,后面你要把这些指令从这个文件中分离出来就没那么容易了。所以那些需要在 App 和 模块中被共享的指令,一定要分离出来到一个文件中,这样也有利于代码的维护

### 不推荐方式###
angular
  .module('app.widgets')

  # order directive that is specific to the order module
  .directive('orderCalendarRange', orderCalendarRange)

  # sales directive that can be used anywhere across the sales app
  .directive('salesCustomerInfo', salesCustomerInfo)

  # spinner directive that can be used anywhere across apps
  .directive('sharedSpinner', sharedSpinner)

  ### implementation details ###


### 推荐方式 ###

 ###
 # @desc order directive that is specific to the order module at a company named Acme
 # @file calendarRange.directive.js
 # @example <div acme-order-calendar-range></div>
 ###
angular
  .module('sales.order')
  .directive('acmeOrderCalendarRange', orderCalendarRange)

 ###
 # @desc spinner directive that can be used anywhere across the sales app at a company named Acme
 # @file customerInfo.directive.js
 # @example <div acme-sales-customer-info></div>
 ###
angular
  .module('sales.widgets')
  .directive('acmeSalesCustomerInfo', salesCustomerInfo)

 ###
 # @desc spinner directive that can be used anywhere across apps at a company named Acme
 # @file spinner.directive.js
 # @example <div acme-shared-spinner></div>
 ###
angular
  .module('shared.widgets')
  .directive('acmeSharedSpinner', sharedSpinner)

  ### implementation details ###

限制DOM的操作

限制DOM的操作,用指令来直接操作DOM,如果有可以替代的方式,如使用css来设置样式,使用 animation 服务,angular 的模版,ngShow 或者 ngHide ,那么就用这些来代替指令。举个例子,如果指令就是定义一个简单的显示和隐藏,那么久用 nghide 和 ngShow 来代替,但是如果指令除了显示隐藏还需要处理更加复杂的事情,那就把显示隐藏和其他需要实现的操作,一起封装到这个指令里,这样能减少 angular 的监听,来提高应用的性能。

  • 对 DOM 的操作不太容易进行测试和调试,我们有更好的办法前提是对DOM的操作比较简单的话(css,animations,templating)

限制元素和属性

限制元素和属性:当创建一个指令,这个指令的如果表现的像一个元素,那么 restrict 设置为 E ,也可以选择设置成 A,,如果这个指令能有他自己的控制器, restrict 设置为 E 是最理想的,不过通常的话,一些引导是将 restrict 设置为 EA,但当指令被封装在独立作用域时,倾向于元素指令表现
,当增强于现有的 DOM 元素,倾向于属性表现

  • 这样做有意义
  • 如果指令倾向于表现得像元素或者属性,这就允许我们定义的指令使用 class 属性

    <!--不推荐方式-->
    <div class="my-calendar-range"></div>
    ### avoid ###
    (->
      myCalendarRange = ()->
          link = (scope, element, attrs)->
            # ... #
    
          directive =
            link: link,
            templateUrl: '/template/is/located/here.html',
            restrict: 'C'
    
          return directive
    
      angular
          .module('app.widgets')
          .directive('myCalendarRange', myCalendarRange)
    )()
    <!-- recommended -->
    <my-calendar-range></my-calendar-range>
    <div my-calendar-range></div>
    
    ### 推荐方式 ###
    (->
    
      myCalendarRange = ()->
    
          link = (scope, element, attrs)->
            # ... #
    
          directive =
              link: link,
              templateUrl: '/template/is/located/here.html',
              restrict: 'EA'
    
          return directive
    
      angular
          .module('app.widgets')
          .directive('myCalendarRange', myCalendarRange)
    )()
    
123
bailinlin

bailinlin

前端,白霸天,博客

22 日志
17 标签
© 2018 bailinlin
由 Hexo 强力驱动
主题 - NexT.Pisces