AngularJS:factory,service与provider的区分

翻译自 http://tylermcginnis.com/angularjs-factory-vs-service-vs-provider/

当你最先运用Angular的时刻,你会发明,你总是会让你的控制器和作用域充溢种种不必要的逻辑。你应当早点意想到一个控制器应当是很简约精华精辟的;同时大多数的贸易逻辑和一些重复性的数据都应当要存储到效劳中。一天我在Stack Overflow上看到一些题目说是斟酌将重复性的数据放在控制器里,然则,这不是这不是一个控制器应当有的目标。假如为了内存须要,控制器就应当在须要他们的时刻实例化,在不须要的时刻就取消掉。因而,Angular在你每次切换路由的时刻,就会清算当前的控制器。然则呢,效劳为我们供应了一种历久存储运用数据的体式格局,同时,也可以在差别的控制器之间一致的运用效劳。

Angular为我们供应了三种建立效劳的体式格局:

1、Factory
2、Service
3、Provider

先简朴引见一下

一、当运用factory来建立效劳的时刻,相当于新建立了一个对象,然后在这个对象上新添属性,末了返回这个对象。当把这个效劳注入控制器的时刻,控制器便可以接见在谁人对象上的属性了。

app.factory('MyFactory', function () {
        var _artist = '',
            service = {};

        service.getArtist = function () {
            return _artist;
        };

        return service;
    })
    .controller('myFactoryCtrl', [
        '$scope', 'MyFactory',
        function ( $scope, MyFactory ) {
            $scope.artist = MyFactory.getArtist();
        }]);

二、当运用service建立效劳的时刻,相当于运用new关键词举行了实例化。因而,你只须要在this上增加属性和要领,然后,效劳就会自动的返回this。当把这个效劳注入控制器的时刻,控制器便可以接见在谁人对象上的属性了。

app.service('MyService', function () {
        var _artist = '';
    
        this.getArtist = function () {
            return _artist;
        };
    })
    .controller('myServiceCtrl', [
        '$scope', 'MyService',
        function ( $scope, MyService ) {
            $scope.artist = MyService.getArtist();
        }]);

三、provider是唯一一种可以建立用来注入到config()函数的效劳的体式格局。想在你的效劳启动之前,举行一些模块化的设置的话,就运用provider

app.provider('MyProvider', function () {

        // 只需直接增加在this上的属性才被config函数接见
        this._artist = '';
        this.thingFromConfig = '';

        // 只需$get函数返回的属性才被控制器接见
        this.$get = function () {
            var that = this;

            return {
                getArtist: function () {
                    return that._artist;
                },
                thingFromConfig: that.thingFromConfig
            };
        };
    })
    .config(['MyProvider', function ( MyProvider ) {
        MyProvider.thingFormConfig = 'this is set in config()';
    }])
    .controller('myProviderCtrl', [
        '$scope', 'MyProvider',
        function ( $scope, MyProvider ) {
            $scope.artist = MyProvider.getArtist();
        }]);

下面我们来细致申明

为了细致的申明这三种体式格局的差别之处,我们离别运用这三种体式格局来建立同一个效劳。这个效劳将会用到iTunes API以及promise的$q

运用factory

要建立和设置效劳,最一般的做法就是运用factory。就像上面简朴申明的那样,这里也没有太多要申明的处所,就是建立一个对象,然后为他增加属性和要领,末了返回这个对象。当把这个效劳注入控制器的时刻,控制器便可以接见在谁人对象上的属性了。一个很一般的例子就像下面那样。

起首我们建立一个对象,然后返回这个对象。

app.factory('MyFactory', function () {
    var service = {};
    
    return service;
});

如今,我们增加到service上的任何属性,只需将MyFactory注入到控制器,控制器就都可以接见了。

如今,我们增加一些私有属性到回调函数里,虽然不能从控制器里直接接见这些变量,然则终究我们会供应一些gettersetter要领到service上以便于我们在须要的时刻修正这些属性。

app.factory('MyFactory', [
    '$http', '$q', function ( $http, $q ) {
        var service = {},
            baseUrl = 'https://itunes.apple.com/search?term=',
            _artist = '',
            _finalUrl = '';

        function makeUrl() {
            _artist = _artist.split(' ').join('+');
            _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK';
            return _finalUrl;
        }

        return service;
    }]);

你应当注重到了,我们没有把这些属性和要领增加到service对象上去。我们如今只是先简朴的建立出来,以便于待会儿运用或许修正。

  • baseUrl是iTunes API须要的基本URL

  • _artist是我们须要查找的艺术家

  • _finalUrl是终究向iTunes发送要求的URL

  • makeUrl是一个用来建立返回我们终究的URL的函数

既然我们的辅佐变量和函数都建立好了,那末,就往service增加一些属性吧。我们在service上增加的任何属性,只需效劳注入了控制器中,那末,控制器便可以接见这些属性。

我们要建立一个setArtist()getArtist()函数来设置以及获得艺术家的值。同时,也要建立一个用于向iTunes发送要求的函数。这个函数会返回一个promise对象,当有数据从iTunes返回的时刻,这个promise对象就会实行。假如你对Angular的promise对象还不是很相识的话,引荐你去深切相识一下。

  • setArtist()接收一个参数而且许可用来设置艺术家的值

  • getArtist()返回艺术家的值

  • callITunes()起首会挪用makeUrl()函数来建立我们须要运用$http举行要求的URL,然后运用我们终究的URL来发送要求,建立一个promise对象。由于$http返回了promise对象,我们便可以在要求今后挪用.success.error了。然后我们处置惩罚从iTunes返回的数据或许驳回,并返回一个毛病音讯,比方There was an error

app.factory('MyFactory', [
    '$http', '$q', function ( $http, $q ) {
        var service = {},
            baseUrl = 'https://itunes.apple.com/search?term=',
            _artist = '',
            _finalUrl = '';

        function makeUrl() {
            _artist = _artist.split(' ').join('+');
            _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK';
            return _finalUrl;
        }

        service.setArtist = function ( artist ) {
            _artist = artist;
        };

        service.getArtist = function () {
            return _artist;
        };

        service.callITunes = function () {
            var deferred = $q.defer();
            _finalUrl = makeUrl();

            $http({
                method: 'JSONP',
                url: _finalUrl
            }).success(function ( data ) {
                deferred.resolve(data);
            }).error(function ( error ) {
                deferred.reject(error);
            });

            return deferred.promise;
        };

        return service;
    }]);

如今,我们的效劳就完成了,我们可以将这个效劳注入到任何的控制器了,而且,可以运用我们增加到service上的那些要领了(getArtist, setArtise, callITunes)。

app.controller('myFactoryCtrl', [
    '$scope', 'MyFactory', function ( $scope, MyFactory ) {
        $scope.data = {};
        $scope.updateArtist = function () {
            MyFactory.setArtist($scope.data.artist);
        };

        $scope.submitArtist = function () {
            MyFactory.callITunes().then(function ( data ) {
                $scope.data.artistData = data;
            }, function ( error ) {
                alert(error);
            });
        };
    }]);

在上面的控制器中我们注入了MyFactory效劳,然后,将从效劳里来的数据设置到$scope的属性上。上面的代码中最难的处所应当就是你从来没有运用过promise。由于callITunes()返回了一个promise对象,所以一旦有数据从iTunes返回,promise实行的时刻,我们便可以运用.then()要领来设置$scope.data.artistData的值了。你会注重到,我们的控制器异常简约,我们一切的逻辑和重复性数据都写在了效劳内里。

运用service

或许在运用service建立效劳时,我们须要晓得的最主要的一件事就是他是运用new关键字举行实例化的。假如你是Javascript巨匠,你应当晓得从代码的实质来思索。关于那些不相识Javascript背景的或许并不熟习new现实做了什么的程序员,我们须要温习一下Javascript的基本学问,以便于终究协助我们明白service的实质。

为了真正的看到当我们运用new来挪用函数的时刻发生了什么,我们来建立一个函数,而且运用new来挪用他,然后,我们再看看在诠释器发明new的时刻,他会做什么。终究效果肯定是一样的。

起首建立我们的组织函数:

function Person( name, age ) {
    this.name = name;
    this.age = age;
}

这是一个典范的组织函数。如今,不管我们什么时刻运用new来挪用这个函数,this都邑被绑定到新建立的谁人对象上。

如今我们再在Person的原型上建立一个要领,以便于每个实例都可以接见到。

Person.prototype.sayName = function () {
    alert('My name is: ' + this.name);
};

如今,由于我们在Person对象的原型上建立了sayName函数,所以,Person的每个实例都可以挪用到这个要领。

既然我们已经有了组织函数和原型要领,那末,就来真正的建立一个Person的实例而且挪用sayName函数:

var tyler = new Person('Tyler', 23);
tyler.sayName();

所以,终究,一切的代码合起来就是下面这个模样:

function Person( name, age ) {
    this.name = name;
    this.age = age;
}

Person.prototype.sayName = function () {
    alert('My name is: ' + this.name);
};

var tyler = new Person('Tyler', 23);
tyler.sayName();

如今我们来看看在运用new的时刻究竟发生了什么。起首你应当注重到的是,在我们的例子中,运用了new今后,我们可以运用tyler来挪用sayName要领,就好像这是一个对象一样,固然,tyler确实是一个对象。所以,我们起首晓得的就是不管我们是不是可以在代码内里瞥见,Person组织函数是会返回一个对象的。第二,我们我们应当晓得,sayName要领是在原型上的,不是直接定义在Person对象实例上的,所以,Person返回的对象必需是经由过程原型托付的。用更简朴的例子说就是,当我们挪用tyler.sayName()的时刻,诠释器就会说:“OK,我将会在刚建立的tyler对象上查找sayName函数,然后挪用他。等会儿,我没有发明这个函数,只看到了nameage属性,让我再检查一下原型。哦,本来在原型上,让我来挪用他”。

下面的代码就是你可以设想的在Javascript里,new现实做了什么。下面的代码是一个很基本的例子,我以诠释器的视角来增加了一些解释:

function Person( name, age ) {
    //var obj = object.create(Person.prototype);
    //this = obj;

    this.name = name;
    this.age = age;
    
    //return thisl
}

如今,既然晓得了new做了什么,那末,运用service来建立效劳也很轻易明白了。

在运用service建立效劳时,我们须要晓得的最主要的一件事就是他是运用new关键字举行实例化的。与上面的例子的学问相结合,你应当便可以意想到你要把属性和要领增加到this上,而且,效劳会自动返回this

与我们运用factory建立效劳的体式格局差别,我们不须要新建立一个对象然后再返回这个对象,由于正如我们前面所提到的那样,我们运用new的时刻,诠释器会自动建立对象,而且代办到他的原型,然后替代我们返回。

所以,在一切的最先之前,我们先建立我们的私有辅佐函数,与我们之前运用factory建立的时刻异常相似。如今我不会诠释每一行的意义了,假如你有什么迷惑的话,可以看看前面的factory的例子。

app.service('MyService', [
    '$http', '$q', function ( $http, $q ) {
        var baseUrl = 'https://itunes.apple.com/search?term=',
            _artist = '',
            _finalUrl = '';

        function makeUrl() {
            _artist = _artist.split(' ').join('+');
            _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK';
            return _finalUrl;
        }
    }]);

如今,我们会把可用的要领都增加到this上。

app.service('MyService', [
    '$http', '$q', function ( $http, $q ) {
        var baseUrl = 'https://itunes.apple.com/search?term=',
            _artist = '',
            _finalUrl = '';

        function makeUrl() {
            _artist = _artist.split(' ').join('+');
            _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK';
            return _finalUrl;
        }

        this.setArtist = function ( artist ) {
            _artist = artist;
        };

        this.getArtist = function () {
            return _artist;
        };

        this.callITunes = function () {
            var deferred = $q.defer();
            _finalUrl = makeUrl();

            $http({
                method: 'JSONP',
                url: _finalUrl
            }).success(function ( data ) {
                deferred.resolve(data);
            }).error(function ( error ) {
                deferred.reject(error);
            });

            return deferred.promise;
        };
    }]);

如今,就像我们运用factory所建立的效劳那样,注入这个效劳的任何一个控制器都可以运用setArtistgetArtistcallITunes要领了。下面是我们的myServiceCtrl,险些与myFactoryCtrl雷同。

app.controller('myServiceCtrl', [
    '$scope', 'MyService', function ( $scope, MyService ) {
        $scope.data = {};
        $scope.updateArtist = function () {
            MyService.setArtist($scope.data.artist);
        };

        $scope.submitArtist = function () {
            MyService.callITunes().then(function ( data ) {
                $scope.data.artistData = data;
            }, function ( error ) {
                alert(error);
            });
        };
    }]);

正如我之前提到的,一旦你明白了new关键词做了什么,servicefactory就险些是雷同的。

运用provider

关于provider,要记着的最主要的一件事就是他是唯一一种可以建立用来注入到app.config()函数的效劳的体式格局。

假如你须要在你的运用在别处运转之前对你的效劳对象举行一部份的设置,那末,这个就显得很主要了。只管与serviceprovider相似,然则我们照样会解说一些他们的差别之处。

起首,相似的,我们设置我们的provider。下面的变量就是我们的私有函数。

app.provider('MyProvider', function () {
    var baseUrl = 'https://itunes.apple.com/search?term=',
        _artist = '',
        _finalUrl = '';
    
    // 从config函数里设置这个属性
    this.thingFromConfig = '';

    function makeUrl() {
        _artist = _artist.split(' ').join('+');
        _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK';
        return _finalUrl;
    }
});

再申明一次,假如对上面的代码逻辑有疑问的话,可以参考之前的列子。

你可以以为provider有三个部份,第一部份是私有变量和私有函数,这些变量和函数会在今后被修正。第二部份是在app.config函数里可以接见的变量和函数,所以,他们可以在在其他处所运用之前被修正。注重,这些变量和函数一定要增加到this上面才行。在我们的例子中,app.config()函数可以修正的只需thingFromConfig。第三部份是在控制器里可以接见的变量和函数。

当运用 provider建立效劳的时刻,唯一可以让控制器接见的属性和要领是在$get()函数里返回的属性和要领。下面的代码将$get增加到了this上面,终究这个函数会被返回。

如今,$get()函数会返回我们须要在控制器里接见的函数和变量。下面是代码例子:

this.$get = function ( $http, $q ) {
            return {
                setArtist: function ( artist ) {
                    _artist = artist;
                },
                getArtist: function () {
                    return _artist;
                },
                callITunes: function () {
                    var deferred = $q.defer();
                    _finalUrl = makeUrl();

                    $http({
                        method: 'JSONP',
                        url: _finalUrl
                    }).success(function ( data ) {
                        deferred.resolve(data);
                    }).error(function ( error ) {
                        deferred.reject(error);
                    });

                    return deferred.promise;
                },
                thingOnConfig: this.thingFromConfig
            };
        };

如今,完全的provider就是这个模样:

app.provider('MyProvider', [
    '$http', '$q', function ( $http, $q ) {
        var baseUrl = 'https://itunes.apple.com/search?term=',
            _artist = '',
            _finalUrl = '';

        this.thingFromConfig = '';

        this.$get = function ( $http, $q ) {
            return {
                setArtist: function ( artist ) {
                    _artist = artist;
                },
                getArtist: function () {
                    return _artist;
                },
                callITunes: function () {
                    var deferred = $q.defer();
                    _finalUrl = makeUrl();

                    $http({
                        method: 'JSONP',
                        url: _finalUrl
                    }).success(function ( data ) {
                        deferred.resolve(data);
                    }).error(function ( error ) {
                        deferred.reject(error);
                    });

                    return deferred.promise;
                },
                thingOnConfig: this.thingFromConfig
            };
        };

        function makeUrl() {
            _artist = _artist.split(' ').join('+');
            _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK';
            return _finalUrl;
        }
    }]);

如今,与之前的servicefactory相似,只需我们把MyProvider注入到控制器内里,对应的要领便可以运用了。下面是myProviderCtrl

app.controller('myProviderCtrl', [
    '$scope', 'MyProvider', function ( $scope, MyProvider ) {
        $scope.data = {};
        $scope.updateArtist = function () {
            MyProvider.setArtist($scope.data.artist);
        };

        $scope.submitArtist = function () {
            MyProvider.callITunes().then(function ( data ) {
                $scope.data.artistData = data;
            }, function ( error ) {
                alert(error);
            });
        };

        $scope.data.thingFromConfig = MyProvider.thingOnConfig;
    }]);

正如之前提到的,运用provider来建立效劳的目标就是为了可以经由过程app.config()函数修正一些变量来通报到终究的项目中。我们来看个例子:

app.config(['MyProviderProvider', function ( MyProviderProvider ) {
    MyProviderProvider.thingFromConfig = 'This sentence was set in app.config. Providers are the only service that can be passed into app.config. Check out the code to see how it works.';
}]);

如今,你便可以看到,在provider里,thingFromConfig是空字符串,然则,当我们在DOM里显现的时刻,他就会是我们上面所设置的字符串了。

谢谢你的浏览,愿望可以协助你区分这三者的差别之处。

要检察完全的代码例子,迎接fork我的项目:https://github.com/tylermcginnis33/AngularServices 或许检察我在Stack Overflow的题目回复

    原文作者:Erichain_Zain
    原文地址: https://segmentfault.com/a/1190000004602085
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞