AngularJS : service, fournisseur et usine

Quelles sont les différences entre un Service, un Provider et un Factory dans AngularJS ?

Solution

Sur la liste de diffusion AngularJS, j'ai obtenu [un fil de discussion étonnant][1] qui explique les notions de service, de fabrique et de fournisseur, ainsi que leur utilisation en matière d'injection. Je compile les réponses :

Services

Syntaxe : module.service( 'serviceName', function );
Résultat : En déclarant serviceName comme un argument injectable on vous fournira une instance de la fonction. En d'autres termes new FunctionYouPassedToService().

Factories

Syntaxe : module.factory('factoryName', function );
Résultat : En déclarant factoryName en tant qu'argument injectable, vous obtiendrez la valeur qui est retournée en invoquant la référence de la fonction passée à module.factory.

Providers

Syntaxe : module.provider('providerName', function );
Résultat : Lorsque vous déclarez providerName en tant qu'argument injectable vous aurez à votre disposition (new ProviderFunction()).$get(). La fonction constructeur est instanciée avant que la méthode $get ne soit appelée - ProviderFunction est la référence de la fonction passée à module.provider.

Les Providers ont l'avantage de pouvoir être configurés pendant la phase de configuration du module.

Voir ici pour le code fourni.

Voici une excellente explication complémentaire par Misko :

provide.value('a', 123);

function Controller(a) {
  expect(a).toEqual(123);
}

Dans ce cas, l'injecteur renvoie simplement la valeur telle quelle. Mais que faire si vous voulez calculer la valeur ? Utilisez alors une fabrique

provide.factory('b', function(a) {
  return a*2;
});

function Controller(b) {
  expect(b).toEqual(246);
}

Donc factory est une fonction qui est responsable de la création de la valeur. Remarquez que la fonction factory peut demander d'autres dépendances.

Mais que faire si vous voulez être plus OO et avoir une classe appelée Greeter ?

function Greeter(a) {
  this.greet = function() {
    return 'Hello ' + a;
  }
}

Pour l'instancier, il faudrait alors écrire

provide.factory('greeter', function(a) {
  return new Greeter(a);
});

Ensuite, nous pourrions demander 'greeter' dans le contrôleur comme ceci

function Controller(greeter) {
  expect(greeter instanceof Greeter).toBe(true);
  expect(greeter.greet()).toEqual('Hello 123');
}

Mais c'est beaucoup trop long. Une façon plus courte d'écrire ceci serait `provider.service('greeter', Greeter);``

Mais que faire si nous voulons configurer la classe Greeter avant l'injection ? Nous pourrions alors écrire

provide.provider('greeter2', function() {
  var salutation = 'Hello';
  this.setSalutation = function(s) {
    salutation = s;
  }

  function Greeter(a) {
    this.greet = function() {
      return salutation + ' ' + a;
    }
  }

  this.$get = function(a) {
    return new Greeter(a);
  };
});

Alors nous pouvons faire ceci :

angular.module('abc', []).config(function(greeter2Provider) {
  greeter2Provider.setSalutation('Halo');
});

function Controller(greeter2) {
  expect(greeter2.greet()).toEqual('Halo 123');
}

En passant, service, factory, et value sont tous dérivés de provider.

provider.service = function(name, Class) {
  provider.provide(name, function() {
    this.$get = function($injector) {
      return $injector.instantiate(Class);
    };
  });
}

provider.factory = function(name, factory) {
  provider.provide(name, function() {
    this.$get = function($injector) {
      return $injector.invoke(factory);
    };
  });
}

provider.value = function(name, value) {
  provider.factory(name, function() {
    return value;
  });
};

[1] : https://groups.google.com/forum/#!msg/angular/56sdORWEoqg/HuZsOsMvKv4J

Commentaires (10)

[JS Fiddle Demo] [1]

" Hello world " exemple avec factory / service / provider :

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

//service style, probably the simplest one
myApp.service('helloWorldFromService', function() {
    this.sayHello = function() {
        return "Hello, World!";
    };
});

//factory style, more involved but more sophisticated
myApp.factory('helloWorldFromFactory', function() {
    return {
        sayHello: function() {
            return "Hello, World!";
        }
    };
});

//provider style, full blown, configurable version     
myApp.provider('helloWorld', function() {

    this.name = 'Default';

    this.$get = function() {
        var name = this.name;
        return {
            sayHello: function() {
                return "Hello, " + name + "!";
            }
        }
    };

    this.setName = function(name) {
        this.name = name;
    };
});

//hey, we can configure a provider!            
myApp.config(function(helloWorldProvider){
    helloWorldProvider.setName('World');
});

function MyCtrl($scope, helloWorld, helloWorldFromFactory, helloWorldFromService) {

    $scope.hellos = [
        helloWorld.sayHello(),
        helloWorldFromFactory.sayHello(),
        helloWorldFromService.sayHello()];
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div ng-controller="MyCtrl">
    {{hellos}}
</div>

[1] : http://jsfiddle.net/pkozlowski_opensource/PxdSP/14/

Commentaires (7)

J'ai remarqué quelque chose d'intéressant en jouant avec les fournisseurs.

La visibilité des injectables est différente pour les providers que pour les services et les factories. Si vous déclarez une "constante" AngularJS (par exemple, myApp.constant('a', 'Robert');), vous pouvez l'injecter dans les services, les fabriques et les fournisseurs.

Mais si vous déclarez une "valeur" AngularJS (par exemple, myApp.value('b', {name : 'Jones'});), vous pouvez l'injecter dans les services et les fabriques, mais PAS dans la fonction de création de fournisseur. Vous pouvez toutefois l'injecter dans la fonction $get que vous définissez pour votre fournisseur. Ceci est mentionné dans la documentation d'AngularJS, mais il est facile de le manquer. Vous pouvez le trouver sur la page %provide dans les sections sur les méthodes value et constant.

http://jsfiddle.net/R2Frv/1/

<div ng-app="MyAppName">
    <div ng-controller="MyCtrl">
        <p>from Service: {{servGreet}}</p>
        <p>from Provider: {{provGreet}}</p>
    </div>
</div>
<script>
    var myApp = angular.module('MyAppName', []);

    myApp.constant('a', 'Robert');
    myApp.value('b', {name: 'Jones'});

    myApp.service('greetService', function(a,b) {
        this.greeter = 'Hi there, ' + a + ' ' + b.name;
    });

    myApp.provider('greetProvider', function(a) {
        this.firstName = a;
        this.$get = function(b) {
            this.lastName = b.name;
            this.fullName = this.firstName + ' ' + this.lastName;
            return this;
        };
    });

    function MyCtrl($scope, greetService, greetProvider) {
        $scope.servGreet = greetService.greeter;
        $scope.provGreet = greetProvider.fullName;
    }
</script>
Commentaires (0)