AngularJS: Servicio vs. Proveedor vs. Fábrica

¿Cuáles son las diferencias entre un Service, Provider y Factory en AngularJS?

Solución

De la lista de correo de AngularJS conseguí un hilo increíble que explica servicio vs fábrica vs proveedor y su uso de inyección. Recopilando las respuestas:

Servicios

Sintaxis: module.service( 'serviceName', function );
Resultado: Al declarar serviceName como argumento inyectable se le proporcionará una instancia de la función. En otras palabras nueva FunciónTuServicio().

Fábricas

Sintaxis: module.factory( 'factoryName', function );
Resultado: Al declarar factoryName como argumento inyectable se le proporcionará el valor que se devuelve al invocar la referencia de la función pasada a module.factory.

Proveedores

Sintaxis: module.provider( 'providerName', function );
Resultado: Al declarar providerName como argumento inyectable se le proporcionará (new ProviderFunction()).$get(). La función constructora es instanciada antes de llamar al método $get - ProviderFunction es la referencia de la función pasada a module.provider.

Los proveedores tienen la ventaja de que pueden ser configurados durante la fase de configuración del módulo.

Ver aquí para el código proporcionado.

Aquí hay una gran explicación adicional de Misko:

provide.value('a', 123);

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

En este caso el inyector simplemente devuelve el valor tal cual. ¿Pero qué pasa si quieres calcular el valor? Entonces utilice una fábrica

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

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

Así que factory es una función que se encarga de crear el valor. Observe que la función de fábrica puede pedir otras dependencias.

¿Pero qué pasa si quieres ser más OO y tener una clase llamada Greeter?

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

Entonces para instanciar tendrías que escribir

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

Entonces podríamos pedir 'greeter' en el controlador así

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

Pero eso es demasiado extenso. Una forma más corta de escribir esto sería provider.service('greeter', Greeter);

¿Pero qué pasa si queremos configurar la clase Greeter antes de la inyección? Entonces podríamos escribir

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);
  };
});

Entonces podemos hacer esto:

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

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

Como nota al margen, service, factory, y value son todos derivados 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;
  });
};
Comentarios (10)

[JS Fiddle Demo][1]

" Hello world " ejemplo con 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>
Comentarios (7)

Me he dado cuenta de algo interesante al jugar con los proveedores.

La visibilidad de los inyectables es diferente para los proveedores que para los servicios y las fábricas. Si declaras una "constante" de AngularJS (por ejemplo, myApp.constant('a', 'Robert');), puedes inyectarla en servicios, fábricas y proveedores.

Pero si declaras un "valor" AngularJS (por ejemplo, myApp.value('b', {nombre: 'Jones'});), puedes inyectarlo en servicios y fábricas, pero NO en la función de creación de proveedores. Sin embargo, puedes inyectarla en la función $get que defines para tu proveedor. Esto se menciona en la documentación de AngularJS, pero es fácil de pasar por alto. Puedes encontrarlo en la página de %provide en las secciones de los métodos value y 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>
Comentarios (0)