按照MVC模式的设计原则,controller应该尽可能保持短小精悍。同样,在AngularJS中,不提倡在 controller中进行DOM操作和数据操作。先来看一个臃肿的、难以维护的controller

var app = angular.module('APPModule', []);
app.controller('MainController', function($scope) {
    $scope.shouldShowLogin = true;
    $scope.showLogin = function() {
        $scope.shouldShowLogin =!$scope.shouldShowLogin;
    };
    $scope.clickButton = function() {
        $('#btnspan').html('Clicked');
    };
    $scope.onLogin = function(user) {
        $http({
            method: 'POST',
            url: '/login',
            data: {
              user: user
            }
        }).success(function(data) {
            //Some Actions
        });
    };
});

初学者总是倾向于写出这种代码,过多的逻辑和前后台交互被放到了本应用来渲染数据的$scope对象上。更加优雅的方法是通过使用servicedirective使controller更轻量,更易于维护。下面来看看如何自定义并使用service,至于directive,我们之后再谈。

1. 创建并使用自定义service

1. 通过factory()创建自定义service
var app = angular.module('APPModule', []);
app.factory('UserService', [ //制定自定义service名为UserService
    '$http',
    function($http) {
        var runLogin = function(user) {
            $http({
                method: 'POST',
                url: '/login',
                data: {
                    user: user
                }
            }).success(function(data) {
                //Some Actions
            });
        };
        
        return {
            runLogin: runLogin
        };
    }
]);

不难发现,如此声明自定义的 service 后,我们将之前臃肿的controller中的登录逻辑放到了 UserService中,代码马上变得逼格十足。

2. 使用自定义service
app.controller('MainController', [
    '$scope',
    'UserService', //在此将自定义的UserService注入
    function($scope, UserService) {
        $scope.onLogin = function(user) {
            //调用UserService的runLogin方法进行登录
            UserService.runLogin(user);
        };
    }
]);

这里的UserService就是我们的自定义service,在controller初始化时注入,即可使用其中的值和方法了,是不是十分方便?注意一点,在自定义服务之前注入所有的AngularJS内置服务,是约定俗称的规则,比如这里我们就是先注入内置的$scope,再注入自定义的UserService。

如果自定义的service和使用它的controller不在同一个module中,需要将service所处的module注入到controller所处的module中:

var serviceModule = angular.module('ServiceModule', []);
serviceModule.factory('UserService', [
    '$http',
    function($http) {
        … //与之前UserService相同
    }
]);

//将service所处的module注入到controller所处的module中
var app = angular.module('APPModule', ['ServiceModule']);
app.controller('MainController', [
  '$scope',
  'UserService',
  function($scope, UserService) {
   … //与之前controller相同
  }
]);

2. 创建自定义service的其他方式

factory()方法是用来创建服务的最常见的方式之一,除此之外,还有一些其他方式可以创建服务。

2.1 使用service()

service()这个方法名知道它是用来创建服务的,它允许为服务对象注册一个构造函数:

app.service('ProductService', function($http) {
    this.getProductList = function() {
        $http({method: 'GET', url: '/api/product'});
    };
});

如果使用和factory()相同的方式,也可以创建服务:

app.service('ProductService', function($http) {
    var getProductList = function() {
        $http({method: 'GET', url: '/api/product'});
    };

    return {
        getProductList: getProductList
    };
});

读者可根据自己的习惯决定具体使用何种方式。

2.2 使用provider()
app.provider('NameService', {
    self: this,
    setName: function(name) {
        self.name = name;
    },
    $get: function() {
        return {
            getName: function() {
                return self.name;
            }
        };
    }
});

使用provider()创建时,返回的对象中必须有$get()属性,其值为一个返回共有函数的函数,如果没有$get属性会报错:

Uncaught Error: [$injector:modulerr] Failed to instantiate module exampleApp due to:
Error: [$injector:pget] Provider 'NameService' must define $get factory method.

provider()创建服务可以在多个应用使用同一个service时获得更强的扩展性,通过如下方式对service进行扩展:

app.config(function(NameServiceProvider) {
    NameServiceProvider.setName('Lucy');
});

其中,NameServiceProvider为对应的服务名(NameService) + Provider组成,通过这一对象可以调用 NameService中的方法,无论该方法有没有被暴露出来。比如,NameService中只暴露出一个getName方法,如果直接在controller中使用NameService.setName('Lucy')会报错:

TypeError: undefined is not a function

但通过provider(),可以为NameService设初始值。

2.3 使用constant()

准确来说,这里要说到的constant()和下面要提到的value()并不算传统意义上MVC中的service,为了方便在这一并讲一下。

该方法可以将变量值注册为服务,可以使该变量被其他modulecontroller共享,是一种在多个modulecontroller共享数据的方法:

app.constant('ASToken', '12345abcde'); //分别对应'key', 'value'
app.controller('MainController', [
    '$scope',
    '$log',
    'ASToken', //注入constant
    function($scope, $log, ASToken) {
        //Some Actions
        $log.info(ASToken); //12345abcde
    }
]);
2.4 使用value()

constant()用法十分相似,唯一的区别是,constant()可以注入到config()中,而value()不可以:

app.constant('ASToken', '12345').config(function(ASToken) {
    //正常使用
});
app.constant('ASValue', '12345').config(function(ASValue) {
    //ASValue在config中无法访问
});
3.AngularJS中的拦截器$provide.decorator():

AngularJS中的$provide服务提供了在服务实例创建时对其进行拦截的功能,可以在有需求的情况下对服务进行扩展修改。不仅可以使用在自定义服务上,也可以对AngularJS的内置服务进行拦截。以拦截2.1中provider()创建的UserService为例:

app.config([
    '$provide’,
    function($provide) {
        $provide.decorator('UserService', [
            '$delegate',
            function($delegate) {
                return{
                    getName: function() {
                        return $delegate.getName() + ' <-- TheFunction Has Been Decorated.'
                    },
                    getAge: function() {
                        return 18;
                    }
                };
            }
        ]);
    }
]);

config()中,通过$provide.decorate()方法拦截UserService,注入的$delegate 可以调用要拦截的service中原本的方法。这里我们通过对UserService 进行拦截,修改了 UserService中的getName()方法,并为UserService添加了getAge()方法。

完。


参考资料:

《AngularJS 权威教程》 作者:Ari Lerner 译者:赵望野 徐飞 何鹏飞