Injecter le service dans app.config

168

Je souhaite injecter un service dans app.config, afin que les données puissent être récupérées avant l'appel du contrôleur. Je l'ai essayé comme ceci:

Un service:

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

Mais j'obtiens cette erreur:

Erreur: fournisseur inconnu: dbService de EditorApp

Comment corriger la configuration et injecter ce service?

dndr
la source
3
malgré ce que vous avez déjà vu, il existe un moyen d'atteindre ce que vous vouliez, et AngularJS a passé beaucoup de temps à activer ce type de fonctionnalité. Passez en revue ma réponse sur la façon d'y parvenir.
Brian Vanderbusch

Réponses:

131

Alex a fourni la bonne raison pour ne pas pouvoir faire ce que vous essayez de faire, donc +1. Mais vous rencontrez ce problème car vous n'utilisez pas tout à fait les résolutions de leur conception.

resolveprend soit la chaîne d'un service, soit une fonction renvoyant une valeur à injecter. Puisque vous faites ce dernier, vous devez passer une fonction réelle:

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

Lorsque le framework va se résoudre data, il injectera le dbServicedans la fonction afin que vous puissiez l'utiliser librement. Vous n'avez pas du tout besoin d'injecter dans le configbloc pour y parvenir.

Bon appétit!

Josh David Miller
la source
2
Merci! Cependant, si je fais cela, j'obtiens: Erreur: «undefined» n'est pas un objet (évaluant «$ q.defer») dans le service.
dndr
1
L'injection se produit dans la fonction de niveau supérieur passée à .service, alors déplacez-vous $qet $httplà.
Josh David Miller
1
@XMLilley La chaîne dans une résolution est en fait le nom d'un service et non une fonction particulière du service. En utilisant votre exemple, vous pourriez le faire pageData: 'myData', mais vous devrez alors appeler pageData.overviewdepuis votre contrôleur. La méthode string n'est probablement utile que si la fabrique de services a renvoyé une promesse au lieu d'une API. Donc, la façon dont vous le faites actuellement est probablement la meilleure.
Josh David Miller
2
@BrianVanderbusch Je dois admettre qu'il y a une certaine confusion sur où exactement vous pensez que nous sommes en désaccord. Le problème réel rencontré par l'OP est qu'il a injecté un service dans un bloc de configuration, ce qui ne peut pas être fait . La solution consiste à injecter le service dans la résolution . Bien que votre réponse ait fourni beaucoup de détails sur la configuration du service, je ne vois pas en quoi cela se rapportait de quelque manière que ce soit à l'erreur rencontrée par l'OP, et votre solution au problème de l'OP était exactement la même : vous avez injecté le service dans la fonction de résolution et pas la fonction de configuration. Pouvez-vous expliquer sur quoi nous ne sommes pas d'accord ici?
Josh David Miller
1
@JoshDavidMiller En utilisant la méthode que j'ai illustrée, il est possible de configurer un service avant l'activation de l'état, de sorte que des erreurs puissent être levées / gérées pendant la phase de configuration, modifiant potentiellement la façon dont vous instancieriez d'autres valeurs de configuration avant le démarrage de l'application. Par exemple, déterminer le rôle d'un utilisateur afin que l'application compile les bonnes fonctionnalités.
Brian Vanderbusch
140

Configurez votre service en tant que fournisseur AngularJS personnalisé

Malgré ce que dit la réponse Acceptée, vous POUVEZ faire ce que vous aviez l'intention de faire, mais vous devez le configurer en tant que fournisseur configurable, afin qu'il soit disponible en tant que service pendant la phase de configuration. Commencez par changer votre Servicefournisseur. comme indiqué ci-dessous. La principale différence ici est qu'après avoir défini la valeur de defer, vous définissez la defer.promisepropriété sur l'objet de promesse renvoyé par $http.get:

Service fournisseur: (fournisseur: recette de service)

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

Maintenant, vous avez un fournisseur personnalisé configurable, il vous suffit de l'injecter. La principale différence ici étant le "fournisseur sur votre injectable" manquant.

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

utiliser des données résolues dans votre 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);
     }

})

Alternatives possibles

L'alternative suivante est une approche similaire, mais permet à la définition de se produire dans le .config, encapsulant le service dans le module spécifique dans le contexte de votre application. Choisissez la méthode qui vous convient. Voir également ci-dessous pour des notes sur une troisième alternative et des liens utiles pour vous aider à comprendre toutes ces choses.

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

Quelques ressources utiles

  • John Lindquist a une excellente explication et démonstration de 5 minutes sur egghead.io , et c'est l'une des leçons gratuites! J'ai essentiellement modifié sa démonstration en la rendant $httpspécifique dans le cadre de cette demande
  • Voir le guide du développeur AngularJS sur les fournisseurs
  • Il y a aussi une excellente explication sur factory/ service/ provider sur clevertech.biz .

Le fournisseur vous donne un peu plus de configuration sur la .serviceméthode, ce qui la rend meilleure en tant que fournisseur de niveau application, mais vous pouvez également l'encapsuler dans l'objet de configuration lui-même en l'injectant $providedans la configuration comme ceci:

Brian Vanderbusch
la source
2
Merci, je cherchais un tel exemple; réponse détaillée et bons liens!
cnlevy
1
aucun problème! C'est ma réponse préférée sur SO. J'ai répondu à cette question alors que la réponse actuellement acceptée avait déjà reçu une réponse et avait 18 voix. Bon pour quelques badges!
Brian Vanderbusch
Ce serait vraiment utile si vos échantillons codepen fonctionnaient. Par exemple, $ provide.service ('dbService', function () {n'a pas injecté $ http mais l'utilise dans son corps. En l'état actuel, je n'ai pas pu faire fonctionner votre technique 2. C'est très frustrant qu'elle soit si difficile de charger les données de configuration à partir d'un fichier de suppression dans un programme angulaire au démarrage.
Bernard

@Alkaline J'ai appris une chose ou deux depuis ce post. La réponse est correcte en théorie, mais a 1 ou 2 choses (1 que vous avez souligné) qui devraient être corrigées. Merci pour le commentaire. Je vais revoir et mettre à jour la réponse. Supprimé le codepen pour le moment ... je n'ai jamais eu la chance de le terminer.
Brian Vanderbusch

5
Je pense que les informations que vous avez fournies ici sont incorrectes. Vous pouvez utiliser un fournisseur mais pendant la phase de configuration, vous ne travaillez pas avec le résultat de l' $getappel. Au lieu de cela, vous souhaitez ajouter des méthodes sur l'instance du fournisseur et revenir simplement thislorsque vous appelez $get. En fait, dans votre exemple, vous pourriez simplement utiliser un service ... Dans un fournisseur, vous ne pouvez pas non plus injecter de services comme $http. Et au fait, ce //return the factory as a provider, that is available during the configuration phasesont des informations trompeuses / incorrectes
Dieterg

21

Réponse courte: vous ne pouvez pas. AngularJS ne vous permettra pas d'injecter des services dans la configuration car il ne peut pas être sûr qu'ils ont été chargés correctement.

Voir cette question et réponse: Injection de dépendance AngularJS de valeur à l'intérieur de module.config

Un module est un ensemble de blocs de configuration et d'exécution qui sont appliqués à l'application pendant le processus de démarrage. Dans sa forme la plus simple, le module consiste en une collection de deux types de blocs:

Blocs de configuration - sont exécutés pendant les enregistrements du fournisseur et la phase de configuration. Seuls les fournisseurs et les constantes peuvent être injectés dans des blocs de configuration. Cela permet d'éviter l'instanciation accidentelle des services avant qu'ils n'aient été entièrement configurés.


2
en fait, cela PEUT être fait. Fournir une réponse expliquant brièvement.
Brian Vanderbusch

5

Je ne pense pas que vous soyez censé pouvoir le faire, mais j'ai réussi à injecter un service dans un configbloc. (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();
                    }
                });
            };
        }
    ]);

Le nom de la méthode de service est dogmaCacheBuster mais .configvous avez écrit cacheBuster (qui n'est défini nulle part dans la réponse) et dogmaCacheBusterProvider (qui n'est plus utilisé). Allez-vous clarifier cela?
diEcho

@ pro.mean Je pense que je montrais la technique que j'ai utilisée, pour insérer un service dans un bloc de configuration, mais cela fait un moment. cacheBusterest défini, en tant que paramètre de la fonction de configuration. En ce qui concerne dogmaCacheBusterProvider, c'est quelque chose d'intelligent qu'Angular fait avec les conventions de nommage, que j'ai oublié depuis longtemps. Cela pourrait vous rapprocher, stackoverflow.com/a/20881705/110010 .
kim3er

sur cette autre référence. J'ai appris à connaître ce fournisseur d' ajout de tout ce que nous définissons dans la .provider()recette. que se passe-t-il si je définis quelque chose avec .factory('ServiceName')ou une .service('ServiceName')recette et que je veux utiliser l'une de ses méthodes en .config bloc, définissez le paramètre sur ServiceNameProvider mais cela arrête mon application.
diEcho

5

Vous pouvez utiliser $ inject service pour injecter un service dans votre configuration

app.config (fonction ($ fournir) {

    $ provide.decorator ("$ exceptionHandler", function ($ delegate, $ injector) {
        fonction de retour (exception, cause) {
            var $ rootScope = $ injector.get ("$ rootScope");
            $ rootScope.addError ({message: "Exception", raison: exception});
            $ delegate (exception, cause);
        };
    });

});

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

Koray Güclü
Merci qui a beaucoup aidé
Erez
Quel hack! Malheureusement, cela ne fonctionnera pas avec la plupart des tests unitaires où l'application n'est pas liée au DOM.
rixo
5

** Demander explicitement des services à d'autres modules en utilisant angular.injector **

Juste pour élaborer sur la réponse de kim3er , vous pouvez fournir des services, des usines, etc. sans les changer en fournisseurs, à condition qu'ils soient inclus dans d'autres modules ...

Cependant, je ne suis pas sûr si le *Provider(qui est créé en interne par angular après avoir traité un service ou une usine) sera toujours disponible (cela peut dépendre de ce qui est chargé en premier), car angular charge paresseusement les modules.

Notez que si vous souhaitez réinjecter les valeurs, elles doivent être traitées comme des constantes.

Voici un moyen plus explicite et probablement plus fiable de le faire + un plunker fonctionnel

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
la source
2

Utilisation de $ injector pour appeler des méthodes de service dans config

J'ai eu un problème similaire et je l'ai résolu en utilisant le service $ injector comme indiqué ci-dessus. J'ai essayé d'injecter le service directement mais je me suis retrouvé avec une dépendance circulaire sur $ http. Le service affiche un modal avec l'erreur et j'utilise le modal ui-bootstrap qui a également une dépendance sur $ 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
la source
Merci qui a beaucoup aidé
Erez
2

Une solution très facile à faire

Remarque : c'est uniquement pour un appel asynchrone, car le service n'est pas initialisé lors de l'exécution de la configuration.

Vous pouvez utiliser la run()méthode. Exemple :

  1. Votre service s'appelle "MyService"
  2. Vous souhaitez l'utiliser pour une exécution asynchrone sur un fournisseur "MyProvider"

Votre code :

(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
la source
1

Eh bien, j'ai eu un peu de mal avec celui-ci, mais je l'ai fait.

Je ne sais pas si les réponses sont obsolètes en raison d'un changement d'angle, mais vous pouvez le faire de cette façon:

Ceci est votre service:

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

Et c'est la config

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

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

Moyen le plus simple: $injector = angular.element(document.body).injector()

Ensuite, utilisez-le pour exécuter invoke()ouget()

Vérification au crayon
la source
Quel hack! Malheureusement, cela ne fonctionnera pas avec la plupart des tests unitaires où l'application n'est pas liée au DOM.
rixo