AngularJS中的服务、提供商和工厂之间有什么区别?


当前回答

TL;博士1) 使用Factory时,您可以创建一个对象,向其添加财产,然后返回相同的对象。当您将此工厂传递到控制器中时,对象上的那些财产现在将通过工厂在该控制器中可用。

app.controller(‘myFactoryCtrl’, function($scope, myFactory){
  $scope.artist = myFactory.getArtist();
});

app.factory(‘myFactory’, function(){
  var _artist = ‘Shakira’;
  var service = {};

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

  return service;
});

2) 当您使用Service时,AngularJS会在后台使用“new”关键字实例化它。因此,您将财产添加到“this”,服务将返回“this“。当您将服务传递到控制器时,“this”上的那些财产现在将通过您的服务在该控制器上可用。

app.controller(‘myServiceCtrl’, function($scope, myService){
  $scope.artist = myService.getArtist();
});

app.service(‘myService’, function(){
  var _artist = ‘Nelly’;
  this.getArtist = function(){
    return _artist;
  }
});

3) 提供程序是唯一可以传递给.config()函数的服务。如果要在服务对象可用之前为其提供模块范围的配置,请使用提供程序。

app.controller(‘myProvider’, function($scope, myProvider){
  $scope.artist = myProvider.getArtist();
  $scope.data.thingFromConfig = myProvider.thingOnConfig;
});

app.provider(‘myProvider’, function(){
 //Only the next two lines are available in the app.config()
 this._artist = ‘’;
 this.thingFromConfig = ‘’;
  this.$get = function(){
    var that = this;
    return {
      getArtist: function(){
        return that._artist;
      },
      thingOnConfig: that.thingFromConfig
    }
  }
});

app.config(function(myProviderProvider){
  myProviderProvider.thingFromConfig = ‘This was set in config’;
});

非TL;博士

1) 工厂工厂是创建和配置服务的最流行方式。除了TL之外,真的没有太多了;DR说。您只需创建一个对象,向其添加财产,然后返回相同的对象。然后,当您将工厂传递到控制器中时,对象上的那些财产现在将通过工厂在该控制器中可用。下面是一个更广泛的示例。

app.factory(‘myFactory’, function(){
  var service = {};
  return service;
});

现在,当我们将“myFactory”传递到我们的控制器时,我们附加到“service”的任何财产都将可用。

现在,让我们向回调函数中添加一些“私有”变量。这些不能从控制器直接访问,但我们最终将在“服务”上设置一些getter/setter方法,以便在需要时更改这些“私有”变量。

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

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

  return service;
});

在这里,您会注意到我们没有将这些变量/函数附加到“服务”。我们只是创建它们,以便以后使用或修改它们。

baseUrl是iTunes API所需的基本URL_艺术家是我们希望查找的艺术家_finalUrl是我们将调用iTunes的最终和完全构建的URLmakeUrl是一个创建并返回iTunes友好URL的函数。

既然我们的helper/private变量和函数已经就位,那么让我们向“service”对象添加一些财产。无论我们在“服务”上放置什么,都可以直接在我们传递“myFactory”到的任何控制器中使用。

我们将创建setArtist和getArtist方法,它们只是返回或设置艺术家。我们还将创建一个方法,用我们创建的URL调用iTunes API。该方法将返回一个承诺,一旦数据从iTunes API返回,该承诺将实现。如果你在AngularJS中没有太多使用承诺的经验,我强烈建议你深入研究一下。

下面的setArtist接受艺术家并允许您设置艺术家。getArtist返回艺术家。callItunes首先调用makeUrl()来构建我们将用于$http请求的URL。然后它设置一个promise对象,用我们的最终url发出一个$http请求,然后因为$http返回一个promice,所以我们可以在请求后调用.success或.error。然后,我们用iTunes数据来解决我们的承诺,或者用一条消息“出现了错误”来拒绝它。

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

  var makeUrl = function(){
    _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(){
    makeUrl();
    var deferred = $q.defer();
    $http({
      method: 'JSONP',
      url: _finalUrl
    }).success(function(data){
      deferred.resolve(data);
    }).error(function(){
      deferred.reject('There was an error')
    })
    return deferred.promise;
  }

  return service;
});

现在我们的工厂完工了。我们现在可以将“myFactory”注入到任何控制器中,然后就可以调用附加到服务对象的方法(setArtist、getArtist和callItunes)。

app.controller('myFactoryCtrl', 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(data){
        alert(data);
      })
  }
});

在上面的控制器中,我们正在注入“myFactory”服务。然后,我们使用“myFactory”中的数据在$scope对象上设置财产。上面唯一棘手的代码是,如果你以前从未处理过承诺。因为callItunes正在返回一个promise,所以我们可以使用.then()方法,并在iTunes数据实现我们的承诺后,才设置$scope.data.artistData。你会注意到我们的控制器非常“薄”(这是一个很好的编码练习)。我们所有的逻辑和持久数据都位于我们的服务中,而不是控制器中。2) 服务在处理创建服务时,最重要的一点可能是它是用“new”关键字实例化的。对于JavaScript专家来说,这应该会给你一个关于代码本质的重要提示。对于那些JavaScript背景有限的人,或者对于那些不太熟悉“new”关键字实际功能的人,让我们回顾一些JavaScript基础知识,这些基础知识最终将帮助我们理解服务的本质。

为了真正了解使用“new”关键字调用函数时发生的更改,让我们创建一个函数并使用“new”关键字调用它,然后让我们展示解释器在看到“new”关键词时所做的操作。最终结果都是一样的。

首先,让我们创建构造函数。

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

这是一个典型的JavaScript构造函数。现在,每当我们使用“new”关键字调用Person函数时,“this”将绑定到新创建的对象。

现在,让我们将一个方法添加到Person的原型中,以便它可以在Person“类”的每个实例上使用。

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

现在,因为我们将sayName函数放在原型上,Person的每个实例都可以调用sayName功能,以提醒该实例的名称。

既然我们在原型上有了Person构造函数和sayName函数,那么让我们实际创建Person的一个实例,然后调用sayName功能。

var tyler = new Person(‘Tyler’, 23);
tyler.sayName(); //alerts ‘My name is Tyler’

因此,创建Person构造函数、向其原型添加函数、创建Person实例,然后在其原型上调用函数的代码看起来都是这样的。

var Person = function(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(); //alerts ‘My name is Tyler’

现在让我们看看在JavaScript中使用“new”关键字时实际发生了什么。首先您应该注意的是,在我们的示例中使用了“new”之后,我们能够像调用对象一样调用“tyler”上的方法(sayName),这是因为它是一个对象。因此,首先,我们知道Person构造函数正在返回一个对象,无论我们是否可以在代码中看到它。其次,我们知道,因为sayName函数位于原型上,而不是直接位于Person实例上,所以Person函数返回的对象必须在查找失败时委托给原型。更简单地说,当我们调用tyler.sayName()时,解释器会说“好的,我要查看我们刚刚创建的‘tyler’对象,找到sayName函数,然后调用它。等一下,我在这里看不到它-我看到的只是名称和年龄,让我检查一下原型。是的,看起来它在原型上,让我调用它”。

下面是如何思考“new”关键字在JavaScript中的实际作用的代码。这基本上是上面一段的代码示例。我已经将“解释器视图”或解释器在注释中看到代码的方式。

var Person = function(name, age){
  //The below line creates an object(obj) that will delegate to the person’s prototype on failed lookups.
  //var obj = Object.create(Person.prototype);

  //The line directly below this sets ‘this’ to the newly created object
  //this = obj;

  this.name = name;
  this.age = age;

  //return this;
}

现在了解了“new”关键字在JavaScript中的真正作用,在AngularJS中创建服务应该更容易理解。

创建服务时最需要理解的是知道服务是用“new”关键字实例化的。结合上述知识和我们的示例,您现在应该认识到,您将把财产和方法直接附加到“this”,然后它将从服务本身返回。让我们来看看这一点。

与我们最初在Factory示例中所做的不同,我们不需要创建一个对象然后返回该对象,因为正如前面多次提到的那样,我们使用了“new”关键字,因此解释器将创建该对象,将其委托给其原型,然后将其返回给我们,而无需我们做任何工作。

首先,让我们创建“private”和helper函数。这看起来应该很熟悉,因为我们对工厂做了完全相同的事情。我不会在这里解释每一行的作用,因为我在工厂示例中做过,如果你感到困惑,请重新阅读工厂示例。

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

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

现在,我们将把控制器中可用的所有方法附加到“this”。

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

  var makeUrl = function(){
    _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(){
    makeUrl();
    var deferred = $q.defer();
    $http({
      method: 'JSONP',
      url: _finalUrl
    }).success(function(data){
      deferred.resolve(data);
    }).error(function(){
      deferred.reject('There was an error')
    })
    return deferred.promise;
  }

});

现在,就像在我们的工厂中一样,setArtist、getArtist和callItunes将在我们传递myService的任何控制器中可用。这是myService控制器(与我们的工厂控制器几乎完全相同)。

app.controller('myServiceCtrl', 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(data){
        alert(data);
      })
  }
});

正如我之前提到的,一旦你真正了解了“新”的作用,服务几乎与AngularJS中的工厂完全相同。3) 提供商

关于提供程序,最重要的一点是它们是您可以传递到应用程序的app.config部分的唯一服务。如果您需要在服务对象的某一部分在应用程序中的其他地方可用之前对其进行更改,这一点非常重要。虽然与服务/工厂非常相似,但我们将讨论一些差异。

首先,我们以与我们的服务和工厂类似的方式建立我们的提供商。下面的变量是我们的“private”和helper函数。

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

  //Going to set this property on the config function below.
  this.thingFromConfig = ‘’;

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

*同样,如果上述代码的任何部分令人困惑,请查看Factory部分,在那里我将更详细地解释它的作用。

您可以将提供商视为有三个部分。第一部分是稍后将修改/设置的“私有”变量/函数(如上所示)。第二部分是变量/函数,这些变量/函数将在app.config函数中可用,因此可以在其他地方可用之前进行更改(如上所示)。需要注意的是,这些变量需要附加到“this”关键字。在我们的示例中,app.config中只有“thingFromConfig”可以更改。第三部分(如下所示)是当您将“myProvider”服务传递到特定控制器中时,控制器中可用的所有变量/函数。

使用Provider创建服务时,控制器中唯一可用的财产/方法是从$get()函数返回的财产/方法。下面的代码将$get放在“this”上(我们知道最终会从该函数返回)。现在,$get函数返回我们希望在控制器中可用的所有方法/财产。下面是一个代码示例。

this.$get = function($http, $q){
    return {
      callItunes: function(){
        makeUrl();
        var deferred = $q.defer();
        $http({
          method: 'JSONP',
          url: _finalUrl
        }).success(function(data){
          deferred.resolve(data);
        }).error(function(){
          deferred.reject('There was an error')
        })
        return deferred.promise;
      },
      setArtist: function(artist){
        _artist = artist;
      },
      getArtist: function(){
        return _artist;
      },
      thingOnConfig: this.thingFromConfig
    }
  }

现在完整的Provider代码如下所示

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

  //Going to set this property on the config function below
  this.thingFromConfig = '';

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

  this.$get = function($http, $q){
    return {
      callItunes: function(){
        makeUrl();
        var deferred = $q.defer();
        $http({
          method: 'JSONP',
          url: _finalUrl
        }).success(function(data){
          deferred.resolve(data);
        }).error(function(){
          deferred.reject('There was an error')
        })
        return deferred.promise;
      },
      setArtist: function(artist){
        _artist = artist;
      },
      getArtist: function(){
        return _artist;
      },
      thingOnConfig: this.thingFromConfig
    }
  }
});

现在,就像在我们的工厂和服务中一样,setArtist、getArtist和callItunes将在我们传递给myProvider的任何控制器中可用。这是myProvider控制器(与我们的工厂/服务控制器几乎完全相同)。

app.controller('myProviderCtrl', 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(data){
        alert(data);
      })
  }

  $scope.data.thingFromConfig = myProvider.thingOnConfig;
});

如前所述,使用Provider创建服务的关键是能够在将最终对象传递给应用程序的其余部分之前,通过app.config函数更改一些变量。让我们看一个例子。

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

现在您可以看到“thingFromConfig”在我们的提供程序中是如何作为空字符串的,但当它显示在DOM中时,它将是“This sentence was set…”。

其他回答

使用此页面和文档作为参考(自上次查看以来似乎有了很大的改进),我整理了以下真实世界演示,其中使用了提供者的5种风格中的4种;价值、常量、工厂和全面供应商。

HTML格式:

<div ng-controller="mainCtrl as main">
    <h1>{{main.title}}*</h1>
    <h2>{{main.strapline}}</h2>
    <p>Earn {{main.earn}} per click</p>
    <p>You've earned {{main.earned}} by clicking!</p>
    <button ng-click="main.handleClick()">Click me to earn</button>
    <small>* Not actual money</small>
</div>

app

var app = angular.module('angularProviders', []);

// A CONSTANT is not going to change
app.constant('range', 100);

// A VALUE could change, but probably / typically doesn't
app.value('title', 'Earn money by clicking');
app.value('strapline', 'Adventures in ng Providers');

// A simple FACTORY allows us to compute a value @ runtime.
// Furthermore, it can have other dependencies injected into it such
// as our range constant.
app.factory('random', function randomFactory(range) {
    // Get a random number within the range defined in our CONSTANT
    return Math.random() * range;
});

// A PROVIDER, must return a custom type which implements the functionality 
// provided by our service (see what I did there?).
// Here we define the constructor for the custom type the PROVIDER below will 
// instantiate and return.
var Money = function(locale) {

    // Depending on locale string set during config phase, we'll
    // use different symbols and positioning for any values we 
    // need to display as currency
    this.settings = {
        uk: {
            front: true,
            currency: '£',
            thousand: ',',
            decimal: '.'
        },
        eu: {
            front: false,
            currency: '€',
            thousand: '.',
            decimal: ','
        }
    };

    this.locale = locale;
};

// Return a monetary value with currency symbol and placement, and decimal 
// and thousand delimiters according to the locale set in the config phase.
Money.prototype.convertValue = function(value) {

    var settings = this.settings[this.locale],
        decimalIndex, converted;

    converted = this.addThousandSeparator(value.toFixed(2), settings.thousand);

    decimalIndex = converted.length - 3;

    converted = converted.substr(0, decimalIndex) +
        settings.decimal +
        converted.substr(decimalIndex + 1);    

    converted = settings.front ?
            settings.currency + converted : 
            converted + settings.currency; 

    return converted;   
};

// Add supplied thousand separator to supplied value
Money.prototype.addThousandSeparator = function(value, symbol) {
   return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, symbol);
};

// PROVIDER is the core recipe type - VALUE, CONSTANT, SERVICE & FACTORY
// are all effectively syntactic sugar built on top of the PROVIDER construct
// One of the advantages of the PROVIDER is that we can configure it before the
// application starts (see config below).
app.provider('money', function MoneyProvider() {

    var locale;

    // Function called by the config to set up the provider
    this.setLocale = function(value) {
        locale = value;   
    };

    // All providers need to implement a $get method which returns
    // an instance of the custom class which constitutes the service
    this.$get = function moneyFactory() {
        return new Money(locale);
    };
});

// We can configure a PROVIDER on application initialisation.
app.config(['moneyProvider', function(moneyProvider) {
    moneyProvider.setLocale('uk');
    //moneyProvider.setLocale('eu'); 
}]);

// The ubiquitous controller
app.controller('mainCtrl', function($scope, title, strapline, random, money) {

    // Plain old VALUE(s)
    this.title = title;
    this.strapline = strapline;

    this.count = 0;

    // Compute values using our money provider    
    this.earn = money.convertValue(random); // random is computed @ runtime
    this.earned = money.convertValue(0);

    this.handleClick = function() { 
        this.count ++;
        this.earned = money.convertValue(random * this.count);
    };
});

工作演示。

下面是我为AngularjS中的对象工厂设计的一些烧烤代码模板。我用汽车/汽车工厂作为例子来说明。在控制器中生成简单的实现代码。

     <script>
        angular.module('app', [])
            .factory('CarFactory', function() {

                /**
                 * BroilerPlate Object Instance Factory Definition / Example
                 */
                this.Car = function() {

                    // initialize instance properties
                    angular.extend(this, {
                        color           : null,
                        numberOfDoors   : null,
                        hasFancyRadio   : null,
                        hasLeatherSeats : null
                    });

                    // generic setter (with optional default value)
                    this.set = function(key, value, defaultValue, allowUndefined) {

                        // by default,
                        if (typeof allowUndefined === 'undefined') {
                            // we don't allow setter to accept "undefined" as a value
                            allowUndefined = false;
                        }
                        // if we do not allow undefined values, and..
                        if (!allowUndefined) {
                            // if an undefined value was passed in
                            if (value === undefined) {
                                // and a default value was specified
                                if (defaultValue !== undefined) {
                                    // use the specified default value
                                    value = defaultValue;
                                } else {
                                    // otherwise use the class.prototype.defaults value
                                    value = this.defaults[key];
                                } // end if/else
                            } // end if
                        } // end if

                        // update 
                        this[key] = value;

                        // return reference to this object (fluent)
                        return this;

                    }; // end this.set()

                }; // end this.Car class definition

                // instance properties default values
                this.Car.prototype.defaults = {
                    color: 'yellow',
                    numberOfDoors: 2,
                    hasLeatherSeats: null,
                    hasFancyRadio: false
                };

                // instance factory method / constructor
                this.Car.prototype.instance = function(params) {
                    return new 
                        this.constructor()
                                .set('color',           params.color)
                                .set('numberOfDoors',   params.numberOfDoors)
                                .set('hasFancyRadio',   params.hasFancyRadio)
                                .set('hasLeatherSeats', params.hasLeatherSeats)
                    ;
                };

                return new this.Car();

            }) // end Factory Definition
            .controller('testCtrl', function($scope, CarFactory) {

                window.testCtrl = $scope;

                // first car, is red, uses class default for:
                // numberOfDoors, and hasLeatherSeats
                $scope.car1     = CarFactory
                                    .instance({
                                        color: 'red'
                                    })
                                ;

                // second car, is blue, has 3 doors, 
                // uses class default for hasLeatherSeats
                $scope.car2     = CarFactory
                                    .instance({
                                        color: 'blue',
                                        numberOfDoors: 3
                                    })
                                ;
                // third car, has 4 doors, uses class default for 
                // color and hasLeatherSeats
                $scope.car3     = CarFactory
                                    .instance({
                                        numberOfDoors: 4
                                    })
                                ;
                // sets an undefined variable for 'hasFancyRadio',
                // explicitly defines "true" as default when value is undefined
                $scope.hasFancyRadio = undefined;
                $scope.car3.set('hasFancyRadio', $scope.hasFancyRadio, true);

                // fourth car, purple, 4 doors,
                // uses class default for hasLeatherSeats
                $scope.car4     = CarFactory
                                    .instance({
                                        color: 'purple',
                                        numberOfDoors: 4
                                    });
                // and then explicitly sets hasLeatherSeats to undefined
                $scope.hasLeatherSeats = undefined;
                $scope.car4.set('hasLeatherSeats', $scope.hasLeatherSeats, undefined, true);

                // in console, type window.testCtrl to see the resulting objects

            });
    </script>

这里有一个更简单的例子。我使用的是一些第三方库,它们期望“Position”对象显示纬度和经度,但通过不同的对象财产。我不想破解供应商代码,所以我调整了我传递的“位置”对象。

    angular.module('app')
.factory('PositionFactory', function() {

    /**
     * BroilerPlate Object Instance Factory Definition / Example
     */
    this.Position = function() {

        // initialize instance properties 
        // (multiple properties to satisfy multiple external interface contracts)
        angular.extend(this, {
            lat         : null,
            lon         : null,
            latitude    : null,
            longitude   : null,
            coords: {
                latitude: null,
                longitude: null
            }
        });

        this.setLatitude = function(latitude) {
            this.latitude           = latitude;
            this.lat                = latitude;
            this.coords.latitude    = latitude;
            return this;
        };
        this.setLongitude = function(longitude) {
            this.longitude          = longitude;
            this.lon                = longitude;
            this.coords.longitude   = longitude;
            return this;
        };

    }; // end class definition

    // instance factory method / constructor
    this.Position.prototype.instance = function(params) {
        return new 
            this.constructor()
                    .setLatitude(params.latitude)
                    .setLongitude(params.longitude)
        ;
    };

    return new this.Position();

}) // end Factory Definition

.controller('testCtrl', function($scope, PositionFactory) {
    $scope.position1 = PositionFactory.instance({latitude: 39, longitude: 42.3123});
    $scope.position2 = PositionFactory.instance({latitude: 39, longitude: 42.3333});
}) // end controller

;

1.服务是在必要时创建的单例对象,在应用程序生命周期结束之前(浏览器关闭时)不会被清理。控制器在不再需要时被销毁和清理。

2.创建服务的最简单方法是使用factory()方法。factory()方法允许我们通过返回包含服务函数和服务数据的对象来定义服务。服务定义函数是我们放置可注入服务的地方,例如$http和$q。前任:

angular.module('myApp.services')
.factory('User', function($http) { // injectables go here
var backendUrl = "http://localhost:3000"; var service = {
    // our factory definition
user: {},
setName: function(newName) {
      service.user['name'] = newName;
    },
setEmail: function(newEmail) { service.user['email'] = newEmail;
},
save: function() {
return $http.post(backendUrl + '/users', { user: service.user
}); }
};
return service; });

在我们的应用程序中使用factory()

在我们的应用程序中使用工厂很容易,因为我们可以在运行时在需要的地方简单地注入工厂。

angular.module('myApp')
.controller('MainController', function($scope, User) {
  $scope.saveUser = User.save;
});

另一方面,service()方法允许我们通过定义构造函数来创建服务。我们可以使用原型对象来定义服务,而不是原始javascript对象。与factory()方法类似,我们还将在函数定义中设置可注入项。创建服务的最低级方法是使用provide()方法。这是创建可以使用.config()函数配置的服务的唯一方法。与前面的to方法不同,我们将在定义的this中设置可注射对象$get()函数定义。

了解AngularJS工厂、服务和提供商

所有这些都用于共享可重用的单例对象。它有助于在应用程序/各种组件/模块之间共享可重用代码。

来自文档服务/工厂:延迟实例化–Angular仅在应用程序组件依赖于服务/工厂时实例化它。单件–每个组件依赖于服务获取对单个实例的引用由服务工厂生成。

工厂

工厂是一个函数,您可以在创建对象之前操作/添加逻辑,然后返回新创建的对象。

app.factory('MyFactory', function() {
    var serviceObj = {};
    //creating an object with methods/functions or variables
    serviceObj.myFunction = function() {
        //TO DO:
    };
    //return that object
    return serviceObj;
});

用法

它可以只是像类一样的函数集合。因此,当您将其注入控制器/工厂/指令函数时,它可以在不同的控制器中实例化。每个应用程序只实例化一次。

服务

在查看服务时,只需考虑阵列原型。服务是一个使用“new”关键字实例化新对象的函数。您可以使用this关键字将财产和函数添加到服务对象。与工厂不同,它不返回任何内容(它返回包含方法/财产的对象)。

app.service('MyService', function() {
    //directly binding events to this context
    this.myServiceFunction = function() {
        //TO DO:
    };
});

用法

当您需要在整个应用程序中共享单个对象时,请使用它。例如,经过验证的用户详细信息、可共享的方法/数据、实用程序功能等。

供应商

提供程序用于创建可配置的服务对象。您可以通过config函数配置服务设置。它使用$get()函数返回一个值。$get函数在运行阶段以角度执行。

app.provider('configurableService', function() {
    var name = '';
    //this method can be be available at configuration time inside app.config.
    this.setName = function(newName) {
        name = newName;
    };
    this.$get = function() {
        var getName = function() {
             return name;
        };
        return {
            getName: getName //exposed object to where it gets injected.
        };
    };
});

用法

当您需要在服务对象可用之前为其提供模块化配置时,例如,假设您希望根据环境(如dev、stage或prod)设置API URL

注释只有提供程序在angular的配置阶段可用,而服务和工厂不是。

希望这能澄清您对工厂、服务和供应商的理解。

TL;博士1) 使用Factory时,您可以创建一个对象,向其添加财产,然后返回相同的对象。当您将此工厂传递到控制器中时,对象上的那些财产现在将通过工厂在该控制器中可用。

app.controller(‘myFactoryCtrl’, function($scope, myFactory){
  $scope.artist = myFactory.getArtist();
});

app.factory(‘myFactory’, function(){
  var _artist = ‘Shakira’;
  var service = {};

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

  return service;
});

2) 当您使用Service时,AngularJS会在后台使用“new”关键字实例化它。因此,您将财产添加到“this”,服务将返回“this“。当您将服务传递到控制器时,“this”上的那些财产现在将通过您的服务在该控制器上可用。

app.controller(‘myServiceCtrl’, function($scope, myService){
  $scope.artist = myService.getArtist();
});

app.service(‘myService’, function(){
  var _artist = ‘Nelly’;
  this.getArtist = function(){
    return _artist;
  }
});

3) 提供程序是唯一可以传递给.config()函数的服务。如果要在服务对象可用之前为其提供模块范围的配置,请使用提供程序。

app.controller(‘myProvider’, function($scope, myProvider){
  $scope.artist = myProvider.getArtist();
  $scope.data.thingFromConfig = myProvider.thingOnConfig;
});

app.provider(‘myProvider’, function(){
 //Only the next two lines are available in the app.config()
 this._artist = ‘’;
 this.thingFromConfig = ‘’;
  this.$get = function(){
    var that = this;
    return {
      getArtist: function(){
        return that._artist;
      },
      thingOnConfig: that.thingFromConfig
    }
  }
});

app.config(function(myProviderProvider){
  myProviderProvider.thingFromConfig = ‘This was set in config’;
});

非TL;博士

1) 工厂工厂是创建和配置服务的最流行方式。除了TL之外,真的没有太多了;DR说。您只需创建一个对象,向其添加财产,然后返回相同的对象。然后,当您将工厂传递到控制器中时,对象上的那些财产现在将通过工厂在该控制器中可用。下面是一个更广泛的示例。

app.factory(‘myFactory’, function(){
  var service = {};
  return service;
});

现在,当我们将“myFactory”传递到我们的控制器时,我们附加到“service”的任何财产都将可用。

现在,让我们向回调函数中添加一些“私有”变量。这些不能从控制器直接访问,但我们最终将在“服务”上设置一些getter/setter方法,以便在需要时更改这些“私有”变量。

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

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

  return service;
});

在这里,您会注意到我们没有将这些变量/函数附加到“服务”。我们只是创建它们,以便以后使用或修改它们。

baseUrl是iTunes API所需的基本URL_艺术家是我们希望查找的艺术家_finalUrl是我们将调用iTunes的最终和完全构建的URLmakeUrl是一个创建并返回iTunes友好URL的函数。

既然我们的helper/private变量和函数已经就位,那么让我们向“service”对象添加一些财产。无论我们在“服务”上放置什么,都可以直接在我们传递“myFactory”到的任何控制器中使用。

我们将创建setArtist和getArtist方法,它们只是返回或设置艺术家。我们还将创建一个方法,用我们创建的URL调用iTunes API。该方法将返回一个承诺,一旦数据从iTunes API返回,该承诺将实现。如果你在AngularJS中没有太多使用承诺的经验,我强烈建议你深入研究一下。

下面的setArtist接受艺术家并允许您设置艺术家。getArtist返回艺术家。callItunes首先调用makeUrl()来构建我们将用于$http请求的URL。然后它设置一个promise对象,用我们的最终url发出一个$http请求,然后因为$http返回一个promice,所以我们可以在请求后调用.success或.error。然后,我们用iTunes数据来解决我们的承诺,或者用一条消息“出现了错误”来拒绝它。

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

  var makeUrl = function(){
    _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(){
    makeUrl();
    var deferred = $q.defer();
    $http({
      method: 'JSONP',
      url: _finalUrl
    }).success(function(data){
      deferred.resolve(data);
    }).error(function(){
      deferred.reject('There was an error')
    })
    return deferred.promise;
  }

  return service;
});

现在我们的工厂完工了。我们现在可以将“myFactory”注入到任何控制器中,然后就可以调用附加到服务对象的方法(setArtist、getArtist和callItunes)。

app.controller('myFactoryCtrl', 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(data){
        alert(data);
      })
  }
});

在上面的控制器中,我们正在注入“myFactory”服务。然后,我们使用“myFactory”中的数据在$scope对象上设置财产。上面唯一棘手的代码是,如果你以前从未处理过承诺。因为callItunes正在返回一个promise,所以我们可以使用.then()方法,并在iTunes数据实现我们的承诺后,才设置$scope.data.artistData。你会注意到我们的控制器非常“薄”(这是一个很好的编码练习)。我们所有的逻辑和持久数据都位于我们的服务中,而不是控制器中。2) 服务在处理创建服务时,最重要的一点可能是它是用“new”关键字实例化的。对于JavaScript专家来说,这应该会给你一个关于代码本质的重要提示。对于那些JavaScript背景有限的人,或者对于那些不太熟悉“new”关键字实际功能的人,让我们回顾一些JavaScript基础知识,这些基础知识最终将帮助我们理解服务的本质。

为了真正了解使用“new”关键字调用函数时发生的更改,让我们创建一个函数并使用“new”关键字调用它,然后让我们展示解释器在看到“new”关键词时所做的操作。最终结果都是一样的。

首先,让我们创建构造函数。

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

这是一个典型的JavaScript构造函数。现在,每当我们使用“new”关键字调用Person函数时,“this”将绑定到新创建的对象。

现在,让我们将一个方法添加到Person的原型中,以便它可以在Person“类”的每个实例上使用。

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

现在,因为我们将sayName函数放在原型上,Person的每个实例都可以调用sayName功能,以提醒该实例的名称。

既然我们在原型上有了Person构造函数和sayName函数,那么让我们实际创建Person的一个实例,然后调用sayName功能。

var tyler = new Person(‘Tyler’, 23);
tyler.sayName(); //alerts ‘My name is Tyler’

因此,创建Person构造函数、向其原型添加函数、创建Person实例,然后在其原型上调用函数的代码看起来都是这样的。

var Person = function(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(); //alerts ‘My name is Tyler’

现在让我们看看在JavaScript中使用“new”关键字时实际发生了什么。首先您应该注意的是,在我们的示例中使用了“new”之后,我们能够像调用对象一样调用“tyler”上的方法(sayName),这是因为它是一个对象。因此,首先,我们知道Person构造函数正在返回一个对象,无论我们是否可以在代码中看到它。其次,我们知道,因为sayName函数位于原型上,而不是直接位于Person实例上,所以Person函数返回的对象必须在查找失败时委托给原型。更简单地说,当我们调用tyler.sayName()时,解释器会说“好的,我要查看我们刚刚创建的‘tyler’对象,找到sayName函数,然后调用它。等一下,我在这里看不到它-我看到的只是名称和年龄,让我检查一下原型。是的,看起来它在原型上,让我调用它”。

下面是如何思考“new”关键字在JavaScript中的实际作用的代码。这基本上是上面一段的代码示例。我已经将“解释器视图”或解释器在注释中看到代码的方式。

var Person = function(name, age){
  //The below line creates an object(obj) that will delegate to the person’s prototype on failed lookups.
  //var obj = Object.create(Person.prototype);

  //The line directly below this sets ‘this’ to the newly created object
  //this = obj;

  this.name = name;
  this.age = age;

  //return this;
}

现在了解了“new”关键字在JavaScript中的真正作用,在AngularJS中创建服务应该更容易理解。

创建服务时最需要理解的是知道服务是用“new”关键字实例化的。结合上述知识和我们的示例,您现在应该认识到,您将把财产和方法直接附加到“this”,然后它将从服务本身返回。让我们来看看这一点。

与我们最初在Factory示例中所做的不同,我们不需要创建一个对象然后返回该对象,因为正如前面多次提到的那样,我们使用了“new”关键字,因此解释器将创建该对象,将其委托给其原型,然后将其返回给我们,而无需我们做任何工作。

首先,让我们创建“private”和helper函数。这看起来应该很熟悉,因为我们对工厂做了完全相同的事情。我不会在这里解释每一行的作用,因为我在工厂示例中做过,如果你感到困惑,请重新阅读工厂示例。

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

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

现在,我们将把控制器中可用的所有方法附加到“this”。

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

  var makeUrl = function(){
    _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(){
    makeUrl();
    var deferred = $q.defer();
    $http({
      method: 'JSONP',
      url: _finalUrl
    }).success(function(data){
      deferred.resolve(data);
    }).error(function(){
      deferred.reject('There was an error')
    })
    return deferred.promise;
  }

});

现在,就像在我们的工厂中一样,setArtist、getArtist和callItunes将在我们传递myService的任何控制器中可用。这是myService控制器(与我们的工厂控制器几乎完全相同)。

app.controller('myServiceCtrl', 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(data){
        alert(data);
      })
  }
});

正如我之前提到的,一旦你真正了解了“新”的作用,服务几乎与AngularJS中的工厂完全相同。3) 提供商

关于提供程序,最重要的一点是它们是您可以传递到应用程序的app.config部分的唯一服务。如果您需要在服务对象的某一部分在应用程序中的其他地方可用之前对其进行更改,这一点非常重要。虽然与服务/工厂非常相似,但我们将讨论一些差异。

首先,我们以与我们的服务和工厂类似的方式建立我们的提供商。下面的变量是我们的“private”和helper函数。

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

  //Going to set this property on the config function below.
  this.thingFromConfig = ‘’;

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

*同样,如果上述代码的任何部分令人困惑,请查看Factory部分,在那里我将更详细地解释它的作用。

您可以将提供商视为有三个部分。第一部分是稍后将修改/设置的“私有”变量/函数(如上所示)。第二部分是变量/函数,这些变量/函数将在app.config函数中可用,因此可以在其他地方可用之前进行更改(如上所示)。需要注意的是,这些变量需要附加到“this”关键字。在我们的示例中,app.config中只有“thingFromConfig”可以更改。第三部分(如下所示)是当您将“myProvider”服务传递到特定控制器中时,控制器中可用的所有变量/函数。

使用Provider创建服务时,控制器中唯一可用的财产/方法是从$get()函数返回的财产/方法。下面的代码将$get放在“this”上(我们知道最终会从该函数返回)。现在,$get函数返回我们希望在控制器中可用的所有方法/财产。下面是一个代码示例。

this.$get = function($http, $q){
    return {
      callItunes: function(){
        makeUrl();
        var deferred = $q.defer();
        $http({
          method: 'JSONP',
          url: _finalUrl
        }).success(function(data){
          deferred.resolve(data);
        }).error(function(){
          deferred.reject('There was an error')
        })
        return deferred.promise;
      },
      setArtist: function(artist){
        _artist = artist;
      },
      getArtist: function(){
        return _artist;
      },
      thingOnConfig: this.thingFromConfig
    }
  }

现在完整的Provider代码如下所示

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

  //Going to set this property on the config function below
  this.thingFromConfig = '';

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

  this.$get = function($http, $q){
    return {
      callItunes: function(){
        makeUrl();
        var deferred = $q.defer();
        $http({
          method: 'JSONP',
          url: _finalUrl
        }).success(function(data){
          deferred.resolve(data);
        }).error(function(){
          deferred.reject('There was an error')
        })
        return deferred.promise;
      },
      setArtist: function(artist){
        _artist = artist;
      },
      getArtist: function(){
        return _artist;
      },
      thingOnConfig: this.thingFromConfig
    }
  }
});

现在,就像在我们的工厂和服务中一样,setArtist、getArtist和callItunes将在我们传递给myProvider的任何控制器中可用。这是myProvider控制器(与我们的工厂/服务控制器几乎完全相同)。

app.controller('myProviderCtrl', 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(data){
        alert(data);
      })
  }

  $scope.data.thingFromConfig = myProvider.thingOnConfig;
});

如前所述,使用Provider创建服务的关键是能够在将最终对象传递给应用程序的其余部分之前,通过app.config函数更改一些变量。让我们看一个例子。

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

现在您可以看到“thingFromConfig”在我们的提供程序中是如何作为空字符串的,但当它显示在DOM中时,它将是“This sentence was set…”。