'angular风格指南'

单一职责

  • 一个文件只定义一个组件

IIFE

  • 使用立即调用函数表达式

Modules

  • 每个独立模块使用唯一的命名
  • 在文件只有一个组件的情况下不需要为一个模块引入一个变量
  • 使用链式语法
  • 只能设置一次
    • 设置module,angular.module('app', []);
    • 获取module,angular.module('app');
  • 回调函数使用命名函数,不要用匿名函数
1
angular
    .module('app')
    .controller('Dashboard', Dashboard);

function Dashboard () { }

Controllers

  • 使用controllerAs语法代替直接用经典的$scope定义的controller的方式
1
<div ng-controller="Customer as customer">
  {{ customer.name }}
</div>

//javascript
function Customer () {
    this.name = {};
    this.sendMessage = function() { };
}
  • 使用controllerAs语法时把this赋值给一个可捕获的变量,选择一个有代表性的名称,如vm代表ViewModel
1
function Customer () {
    var vm = this;
    vm.name = {};
    vm.sendMessage = function() { };
}
  • 使用下面的做法避免jshint的警告。但是构造函数是不需要这个的
1
/* jshint validthis: true */
var vm = this;
  • 在controller中使用controllAs创建watch时,可以用下面的语法监测vm.*的成员(创建watch时要谨慎,因为它会增加更多的负载)
1
<input ng-model="vm.title"/>

function SomeController($scope, $log) {
    var vm = this;
    vm.title = 'Some Title';

    $scope.$watch('vm.title', function(current, original) {
        $log.info('vm.title was %s', original);
        $log.info('vm.title is now %s', current);
    });
}
  • 可绑定成员放到controller顶部,按字母排序,并且不要通过controller代码传播
    • 如果函数只有一行,可以把它放顶部
1
function Sessions() {
    var vm = this;

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

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

    function gotoSession() {
      /* */
    }

    function refresh() {
      /* */
    }
}
  • 使用函数声明来隐藏实现细节,把绑定成员放到顶部
1
function Avengers(dataservice, 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 dataservice.getAvengers().then(function(data) {
            vm.avengers = data;
            return vm.avengers;
        });
    }
}
  • 通过委派到server和factory中来延迟controller中的逻辑
1
function Order (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(showServiceError);
    };
}
  • 保持controller的专一性
    • 一个view只定义一个controller,尽量不要在其它的view中使用这个controller,把可重用的逻辑放到factory中,保证controller只服务当前视图
  • 当一个controller必须匹配一个view时或者任何组件可能被其它controller或是view重用时,连同controller的route一起定义
1
// avengers.html 
<div ng-controller="Avengers as vm">
</div>

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

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

Services

  • 用new 实例化services,用this实例化公共方法和变量,由于这和factory是类似的,为了统一,推荐使用factory
  • 所有的angular services都是单例,意味着第个injector都只有一个实例化的service
1
// 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) {
          /* */
        }
    };
}

Factories

  • factory应该是单一职责,这是由上下文进行封装的。一旦一个factory要处理超过单一的目的地,就应该再创建一个
  • factory是一个单例,它返回一个包含service成员的对象
  • 使用从显露模块模式派生出来的技术把service(它接口)中可调用的成员暴露到顶部
1
/* recommended */
function dataService () {
    var someValue = '';
    var service = {
        save: save,
        someValue: someValue,
        validate: validate
    };
    return service;

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

    function save () {
      /* */
    };

    function validate () {
      /* */
    };
}
  • 函数声明隐藏实现细节,把绑定成员放到顶部,当你需要在controller中绑定一个函数时,把它指向一个函数声明,这个函数声明在文件的后面会出现
1
function dataservice($http, $location, $q, exception, logger) {
    var isPrimed = false;
    var primePromise;

    var service = {
        getAvengersCast: getAvengersCast,
        getAvengerCount: getAvengerCount,
        getAvengers: getAvengers,
        ready: ready
    };

    return service;

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

    function getAvengers() {
      // implementation details go here
    }

    function getAvengerCount() {
      // implementation details go here
    }

    function getAvengersCast() {
      // implementation details go here
    }

    function prime() {
      // implementation details go here
    }

    function ready(nextPromises) {
      // implementation details go here
    }
}

Data Services

  • 把进行数据操作和数据交互的逻辑放到factory中,数据服务负责XHR请求、本地存储、内存存储和其它任何数据操作
1
angular
    .module('app.core')
    .factory('dataservice', dataservice);

dataservice.$inject = ['$http', 'logger'];

function dataservice($http, logger) {
    return {
        getAvengers: getAvengers
    };

    function getAvengers() {
        return $http.get('/api/maa')
            .then(getAvengersComplete)
            .catch(getAvengersFailed);

        function getAvengersComplete(response) {
            return response.data.results;
        }

        function getAvengersFailed(error) {
            logger.error('XHR Failed for getAvengers.' + error.data);
        }
    }
}
  • 数据服务被调用时(如controller),隐藏调用的直接行为
1
angular
    .module('app.avengers')
    .controller('Avengers', Avengers);

Avengers.$inject = ['dataservice', 'logger'];

function Avengers(dataservice, logger) {
    var vm = this;
    vm.avengers = [];

    activate();

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

    function getAvengers() {
        return dataservice.getAvengers()
          .then(function(data) {
              vm.avengers = data;
              return vm.avengers;
          });
    }
}
  • 像$http一样,数据调用返回一个Promise, 在你调用的函数也返回一个promise
    为什么 : 可以把promise连在一起,在数据调用完成并resolve或reject这个promise后采取进一步的行为
1
activate();

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

function getAvengers() {
    //step2
    return dataservice.getAvengers()
      .then(function(data) {
          //step3
          vm.avengers = data;
          return vm.avengers;
      });
}

Directives

  • 一个文件只创建一个directive,并依照directive来命名
1
// customerInfo.directive.js
angular
    .module('sales.widgets')
    .directive('acmeSalesCustomerInfo', salesCustomerInfo);

function salesCustomerInfo() {
    
};

// spinner.directive.js
angular
    .module('shared.widgets')
    .directive('acmeSharedSpinner', sharedSpinner);

function sharedSpinner() {

};
  • 当需要操作DOM时,使用directive,也可以用css设置样式\animation\services\angular模版、ngShow或者ngHide
  • 提供一个短小、唯一、具有描述性的directive前缀,例如acmeSalesCustomerInfo在HTML中声明为acme-sales-customer-info
  • 限制元素和属性,一般原则是允许EA,1.3+默认使用EA
1
angular
    .module('app.widgets')
    .directive('myCalendarRange', myCalendarRange);

function myCalendarRange () {
    var directive = {
        link: link,
        templateUrl: '/template/is/located/here.html',
        restrict: 'EA'
    };
    return directive;

    function link(scope, element, attrs) {
      /* */
    }
}
  • directive使用controller as语法,和view使用controller as 保持一致
  • 当directive中使用了controller as 语法时,使用bindToController = true把父级作用域绑定到directive的controller作用域
  • 在activate函数中解决controller的启动逻辑
1
function Avengers(dataservice) {
    var vm = this;
    vm.avengers = [];
    vm.title = 'Avengers';

    activate();

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

    function activate() {
        return dataservice.getAvengers().then(function(data) {
            vm.avengers = data;
            return vm.avengers;
        });
    }
}
  • 当你决定过渡到视图前取消路由,使用route resolve
1
// route-config.js
angular
    .module('app')
    .config(config);

function config ($routeProvider) {
    $routeProvider
        .when('/avengers', {
            templateUrl: 'avengers.html',
            controller: 'Avengers',
            controllerAs: 'vm',
            resolve: {
                moviesPrepService: function(movieService) {
                    return movieService.getMovies();
                }
            }
        });
}

// avengers.js
angular
    .module('app')
    .controller('Avengers', Avengers);

Avengers.$inject = ['moviesPrepService'];
function Avengers (moviesPrepService) {
    var vm = this;
    vm.movies = moviesPrepService.movies;
}
  • 用$inject手动添加组件所需要的依赖
    • 避免压缩后找不到变量
    • 避免创建内嵌的依赖
1
angular
    .module('app')
    .controller('Dashboard', Dashboard);

Dashboard.$inject = ['$location', '$routeParams', 'common', 'dataservice'];

function Dashboard($location, $routeParams, common, dataservice) {
}