Inyectar servicio en app.config

168

Quiero inyectar un servicio en app.config, para que los datos puedan recuperarse antes de que se llame al controlador. Lo intenté así:

Servicio:

app.service('dbService', function() {
    return {
        getData: function($q, $http) {
            var defer = $q.defer();
            $http.get('db.php/score/getData').success(function(data) {
                defer.resolve(data);            
            });
            return defer.promise;
        }
    };
});

Config:

app.config(function ($routeProvider, dbService) {
    $routeProvider
        .when('/',
        {
            templateUrl: "partials/editor.html",
            controller: "AppCtrl",
            resolve: {
                data: dbService.getData(),
            }
        })
});

Pero me sale este error:

Error: proveedor desconocido: dbService de EditorApp

¿Cómo corregir la configuración e inyectar este servicio?

dndr
fuente
3
a pesar de lo que ha visto ya, no es una manera de lograr lo que pretende, y AngularJS ha pasado mucho tiempo que permite este tipo de funcionalidad. Revise mi respuesta sobre cómo lograr esto.
Brian Vanderbusch

Respuestas:

131

Alex proporcionó la razón correcta para no poder hacer lo que estás tratando de hacer, entonces +1. Pero está encontrando este problema porque no está utilizando del todo resuelve cómo están diseñados.

resolvetoma la cadena de un servicio o una función que devuelve un valor a inyectar. Como estás haciendo lo último, debes pasar una función real:

resolve: {
  data: function (dbService) {
    return dbService.getData();
  }
}

Cuando el marco se resuelva data, inyectará dbServiceen la función para que pueda usarlo libremente. No necesita inyectarse en el configbloque para lograr esto.

¡Buen provecho!

Josh David Miller
fuente
2
¡Gracias! Sin embargo, si hago esto me sale: Error: 'undefined' no es un objeto (evaluando '$ q.defer') en el servicio.
dndr
1
La inyección se produce en la función de nivel superior que se pasa .service, así que muévase $qy $httpallí.
Josh David Miller
1
@XMLilley La cadena en una resolución es en realidad el nombre de un servicio y no una función particular en el servicio. Usando su ejemplo, podría hacerlo pageData: 'myData', pero luego tendría que llamar pageData.overviewdesde su controlador. El método de cadena probablemente solo sea útil si la fábrica de servicios devolvió una promesa en lugar de una API. Entonces, la forma en que lo estás haciendo es probablemente la mejor.
Josh David Miller
2
@BrianVanderbusch Debo admitir cierta confusión sobre dónde exactamente crees que no estamos de acuerdo. El problema real que encontró el OP fue que inyectó un servicio en un bloque de configuración, lo que no se puede hacer . La solución es inyectar el servicio en la resolución . Si bien su respuesta proporcionó muchos detalles sobre la configuración del servicio, no veo cómo se relacionó de ninguna manera con el error que encontró el OP, y su solución al problema del OP fue exactamente la misma : inyectó el servicio en la función de resolución y No es la función de configuración. ¿Puedes explicar dónde estamos en desacuerdo aquí?
Josh David Miller
1
@JoshDavidMiller Usando el método que demostré, es posible configurar un servicio antes de la activación del estado, de modo que los errores se puedan arrojar / manejar durante la fase de configuración, alterando potencialmente la forma en que instanciaría otros valores de configuración antes del arranque de la aplicación. Por ejemplo, determinar el rol de un usuario para que la aplicación compile las características correctas.
Brian Vanderbusch
140

Configure su servicio como un proveedor personalizado de AngularJS

A pesar de lo que dice la respuesta aceptada, en realidad PUEDES hacer lo que pretendías hacer, pero debes configurarlo como un proveedor configurable, para que esté disponible como un servicio durante la fase de configuración. Primero, cambia tu Servicea un proveedor Como se muestra abajo. La diferencia clave aquí es que después de establecer el valor de defer, establece la defer.promisepropiedad en el objeto de promesa devuelto por $http.get:

Servicio de proveedor: (proveedor: receta de servicio)

app.provider('dbService', function dbServiceProvider() {

  //the provider recipe for services require you specify a $get function
  this.$get= ['dbhost',function dbServiceFactory(dbhost){
     // return the factory as a provider
     // that is available during the configuration phase
     return new DbService(dbhost);  
  }]

});

function DbService(dbhost){
    var status;

    this.setUrl = function(url){
        dbhost = url;
    }

    this.getData = function($http) {
        return $http.get(dbhost+'db.php/score/getData')
            .success(function(data){
                 // handle any special stuff here, I would suggest the following:
                 status = 'ok';
                 status.data = data;
             })
             .error(function(message){
                 status = 'error';
                 status.message = message;
             })
             .then(function(){
                 // now we return an object with data or information about error 
                 // for special handling inside your application configuration
                 return status;
             })
    }    
}

Ahora, tiene un proveedor personalizado configurable, solo necesita inyectarlo. La diferencia clave aquí es la falta de "Proveedor en su inyectable".

config:

app.config(function ($routeProvider) { 
    $routeProvider
        .when('/', {
            templateUrl: "partials/editor.html",
            controller: "AppCtrl",
            resolve: {
                dbData: function(DbService, $http) {
                     /*
                     *dbServiceProvider returns a dbService instance to your app whenever
                     * needed, and this instance is setup internally with a promise, 
                     * so you don't need to worry about $q and all that
                     */
                    return DbService('http://dbhost.com').getData();
                }
            }
        })
});

utilizar datos resueltos en su appCtrl

app.controller('appCtrl',function(dbData, DbService){
     $scope.dbData = dbData;

     // You can also create and use another instance of the dbService here...
     // to do whatever you programmed it to do, by adding functions inside the 
     // constructor DbService(), the following assumes you added 
     // a rmUser(userObj) function in the factory
     $scope.removeDbUser = function(user){
         DbService.rmUser(user);
     }

})

Posibles alternativas

La siguiente alternativa es un enfoque similar, pero permite que la definición ocurra dentro del .configencapsulado del servicio dentro del módulo específico en el contexto de su aplicación. Elija el método adecuado para usted. También vea a continuación las notas sobre una tercera alternativa y enlaces útiles para ayudarlo a entender todas estas cosas

app.config(function($routeProvider, $provide) {
    $provide.service('dbService',function(){})
    //set up your service inside the module's config.

    $routeProvider
        .when('/', {
            templateUrl: "partials/editor.html",
            controller: "AppCtrl",
            resolve: {
                data: 
            }
        })
});

Algunos recursos útiles

  • John Lindquist tiene una excelente explicación y demostración de 5 minutos de esto en egghead.io , ¡y es una de las lecciones gratuitas! Básicamente modifiqué su demostración haciéndola $httpespecífica en el contexto de esta solicitud
  • Ver la guía para desarrolladores de AngularJS sobre proveedores
  • También hay una excelente explicación sobre factory/ service/ provider en clevertech.biz .

El proveedor le brinda un poco más de configuración sobre el .servicemétodo, lo que lo hace mejor como proveedor de nivel de aplicación, pero también puede encapsular esto dentro del propio objeto de configuración al inyectar $provideen la configuración de la siguiente manera:

Brian Vanderbusch
fuente
2
Gracias, estaba buscando ese ejemplo; respuesta detallada y excelentes enlaces!
cnlevy
1
¡No hay problema! Esta es mi respuesta favorita en SO. Respondí esto cuando la respuesta actualmente aceptada ya había sido respondida y tenía 18 votos. ¡Bueno para algunas insignias!
Brian Vanderbusch
Realmente sería útil si tus muestras de codepen funcionaran. Por ejemplo, $ provide.service ('dbService', function () {no tiene inyectado $ http pero lo usa en su cuerpo. Tal como está, no pude hacer que tu técnica 2 funcione. Es muy frustrante que sea Es muy difícil cargar los datos de configuración de un archivo de eliminación en un programa angular al inicio.
Bernard

@Alkaline He aprendido una o dos cosas desde esta publicación. La respuesta es correcta en teoría, pero tiene 1 o 2 cosas (1 que señaló) que deberían ser reparadas. Gracias por el comentario. Revisaré y actualizaré la respuesta. Eliminé el codepen por el momento ... nunca tuve la oportunidad de terminarlo.
Brian Vanderbusch

55
Creo que la información que proporcionó aquí es incorrecta. Puede usar un proveedor pero durante la fase de configuración no está trabajando con el resultado de la $getllamada. En su lugar, desea agregar métodos en la instancia del proveedor y simplemente regresar thiscuando llame $get. De hecho, en su ejemplo, podría usar un servicio ... En un proveedor tampoco puede inyectar servicios como $http. Y por cierto esto //return the factory as a provider, that is available during the configuration phasees información engañosa / incorrecta
Dieterg

21

Respuesta corta: no puedes. AngularJS no le permitirá inyectar servicios en la configuración porque no puede estar seguro de que se hayan cargado correctamente.

Vea esta pregunta y respuesta: inyección de valor de dependencia AngularJS dentro del módulo.config

Un módulo es una colección de bloques de configuración y ejecución que se aplican a la aplicación durante el proceso de arranque. En su forma más simple, el módulo consiste en la colección de dos tipos de bloques:

Bloques de configuración : se ejecutan durante los registros de proveedores y la fase de configuración. Solo los proveedores y las constantes pueden inyectarse en bloques de configuración. Esto es para evitar la creación de instancias accidentales de los servicios antes de que se hayan configurado completamente.


2
en realidad, esto PUEDE hacerse. Proporcionando una respuesta en breve explicando.
Brian Vanderbusch

5

No creo que se supone que puedas hacer esto, pero he inyectado con éxito un servicio en un configbloque. (AngularJS v1.0.7)

angular.module('dogmaService', [])
    .factory('dogmaCacheBuster', [
        function() {
            return function(path) {
                return path + '?_=' + Date.now();
            };
        }
    ]);

angular.module('touch', [
        'dogmaForm',
        'dogmaValidate',
        'dogmaPresentation',
        'dogmaController',
        'dogmaService',
    ])
    .config([
        '$routeProvider',
        'dogmaCacheBusterProvider',
        function($routeProvider, cacheBuster) {
            var bust = cacheBuster.$get[0]();

            $routeProvider
                .when('/', {
                    templateUrl: bust('touch/customer'),
                    controller: 'CustomerCtrl'
                })
                .when('/screen2', {
                    templateUrl: bust('touch/screen2'),
                    controller: 'Screen2Ctrl'
                })
                .otherwise({
                    redirectTo: bust('/')
                });
        }
    ]);

angular.module('dogmaController', [])
    .controller('CustomerCtrl', [
        '$scope',
        '$http',
        '$location',
        'dogmaCacheBuster',
        function($scope, $http, $location, cacheBuster) {

            $scope.submit = function() {
                $.ajax({
                    url: cacheBuster('/customers'),  //server script to process data
                    type: 'POST',
                    //Ajax events
                    // Form data
                    data: formData,
                    //Options to tell JQuery not to process data or worry about content-type
                    cache: false,
                    contentType: false,
                    processData: false,
                    success: function() {
                        $location
                            .path('/screen2');

                        $scope.$$phase || $scope.$apply();
                    }
                });
            };
        }
    ]);

el nombre del método de servicio es dogmaCacheBuster pero en .configusted ha escrito cacheBuster (que no está definido en ninguna parte de la respuesta) y dogmaCacheBusterProvider (que no se usa más). ¿Vas a aclarar sobre esto?
diEcho

@ pro.mean Creo que estaba demostrando la técnica que utilicé para insertar un servicio en un bloque de configuración, pero ha pasado un tiempo. cacheBusterestá definido, como un parámetro de la función de configuración. Con respecto a esto dogmaCacheBusterProvider, es algo inteligente que Angular hace con las convenciones de nombres, que hace mucho tiempo he olvidado. Esto podría acercarte, stackoverflow.com/a/20881705/110010 .
kim3er

sobre esto otra referencia. Llegué a saber que agregar Proveedor lo que definimos en la .provider()receta. ¿Qué pasa si defino algo con .factory('ServiceName')o .service('ServiceName')receta y quiero usar uno de sus métodos en .config bloque? Establezco el parámetro como ServiceNameProvider pero detiene mi aplicación.
diEcho

5

Puede usar el servicio $ inject para inyectar un servicio en su configuración

app.config (function ($ provide) {

    $ provide.decorator ("$ exceptionHandler", función ($ delegado, $ inyector) {
        función de retorno (excepción, causa) {
            var $ rootScope = $ injector.get ("$ rootScope");
            $ rootScope.addError ({mensaje: "Excepción", razón: excepción});
            $ delegado (excepción, causa);
        };
    });

});

Fuente: http://odetocode.com/blogs/scott/archive/2014/04/21/better-error-handling-in-angularjs.aspx

Koray Güclü
Gracias que ayudaron mucho
Erez
¡Qué truco! Desafortunadamente, no funcionará con la mayoría de las pruebas unitarias donde la aplicación no está vinculada al DOM.
rixo
5

** Solicitar servicios explícitamente de otros módulos usando angular.injector **

Solo para explicar la respuesta de kim3er , puede proporcionar servicios, fábricas, etc. sin cambiarlos a proveedores, siempre que estén incluidos en otros módulos ...

Sin embargo, no estoy seguro de si el *Provider(que se realiza internamente por angular después de que procesa un servicio o una fábrica) siempre estará disponible (puede depender de qué más se cargue primero), ya que angular carga perezosamente los módulos.

Tenga en cuenta que si desea volver a inyectar los valores, deben tratarse como constantes.

Aquí hay una forma más explícita y probablemente más confiable de hacerlo + un explotador en funcionamiento

var base = angular.module('myAppBaseModule', [])
base.factory('Foo', function() { 
  console.log("Foo");
  var Foo = function(name) { this.name = name; };
  Foo.prototype.hello = function() {
    return "Hello from factory instance " + this.name;
  }
  return Foo;
})
base.service('serviceFoo', function() {
  this.hello = function() {
    return "Service says hello";
  }
  return this;
});

var app = angular.module('appModule', []);
app.config(function($provide) {
  var base = angular.injector(['myAppBaseModule']);
  $provide.constant('Foo', base.get('Foo'));
  $provide.constant('serviceFoo', base.get('serviceFoo'));
});
app.controller('appCtrl', function($scope, Foo, serviceFoo) {
  $scope.appHello = (new Foo("app")).hello();
  $scope.serviceHello = serviceFoo.hello();
});
00500005
fuente
2

Usar $ injector para llamar a los métodos de servicio en la configuración

Tuve un problema similar y lo resolví utilizando el servicio $ injector como se muestra arriba. Intenté inyectar el servicio directamente pero terminé con una dependencia circular en $ http. El servicio muestra un modal con el error y estoy usando el modal ui-bootstrap que también depende de $ https.

    $httpProvider.interceptors.push(function($injector) {
    return {
        "responseError": function(response) {

            console.log("Error Response status: " + response.status);

            if (response.status === 0) {
                var myService= $injector.get("myService");
                myService.showError("An unexpected error occurred. Please refresh the page.")
            }
        }
    }
Lincoln Spiteri
fuente
Gracias que ayudaron mucho
Erez
2

Una solución muy fácil de hacer.

Nota : es solo para una llamada asíncrona, porque el servicio no se inicializa en la ejecución de la configuración.

Puedes usar el run()método. Ejemplo:

  1. Su servicio se llama "MyService"
  2. Desea usarlo para una ejecución asíncrona en un proveedor "MyProvider"

Tu codigo :

(function () { //To isolate code TO NEVER HAVE A GLOBAL VARIABLE!

    //Store your service into an internal variable
    //It's an internal variable because you have wrapped this code with a (function () { --- })();
    var theServiceToInject = null;

    //Declare your application
    var myApp = angular.module("MyApplication", []);

    //Set configuration
    myApp.config(['MyProvider', function (MyProvider) {
        MyProvider.callMyMethod(function () {
            theServiceToInject.methodOnService();
        });
    }]);

    //When application is initialized inject your service
    myApp.run(['MyService', function (MyService) {
        theServiceToInject = MyService;
    }]);
});
Chklang
fuente
1

Bueno, luché un poco con este, pero en realidad lo hice.

No sé si las respuestas están desactualizadas debido a algún cambio en angular, pero puede hacerlo de esta manera:

Este es tu servicio:

.factory('beerRetrievalService', function ($http, $q, $log) {
  return {
    getRandomBeer: function() {
      var deferred = $q.defer();
      var beer = {};

      $http.post('beer-detail', {})
      .then(function(response) {
        beer.beerDetail = response.data;
      },
      function(err) {
        $log.error('Error getting random beer', err);
        deferred.reject({});
      });

      return deferred.promise;
    }
  };
 });

Y esta es la configuración

.when('/beer-detail', {
  templateUrl : '/beer-detail',
  controller  : 'productDetailController',

  resolve: {
    beer: function(beerRetrievalService) {
      return beerRetrievalService.getRandomBeer();
    }
  }
})
Luis Sieira
fuente
0

La manera más fácil: $injector = angular.element(document.body).injector()

Luego usa eso para correr invoke()oget()

Pencilcheck
fuente
¡Qué truco! Desafortunadamente, no funcionará con la mayoría de las pruebas unitarias donde la aplicación no está vinculada al DOM.
rixo