Inietti il ​​servizio in app.config


168

Voglio iniettare un servizio in app.config, in modo che i dati possano essere recuperati prima che venga chiamato il controller. L'ho provato in questo modo:

Servizio:

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

Ma ottengo questo errore:

Errore: provider sconosciuto: dbService da EditorApp

Come correggere l'installazione e iniettare questo servizio?


3
nonostante quello che avete visto già, c'è un modo per ottenere ciò che si intendeva, e AngularJS ha speso un sacco di tempo che consente questo tipo di funzionalità. Rivedi la mia risposta su come raggiungere questo obiettivo.
Brian Vanderbusch,

Risposte:


131

Alex ha fornito il motivo corretto per non essere in grado di fare ciò che stai cercando di fare, quindi +1. Ma stai riscontrando questo problema perché non stai usando risolve il modo in cui sono progettati.

resolveaccetta la stringa di un servizio o una funzione che restituisce un valore da iniettare. Dato che stai facendo quest'ultimo, devi passare una funzione reale:

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

Quando il framework si risolve data, inietterà dbServicela funzione in modo da poterla usare liberamente. Non è necessario iniettare affatto nel configblocco per ottenere questo risultato.

Buon appetito!


2
Grazie! Tuttavia, se lo faccio ottengo: Errore: "undefined" non è un oggetto (che valuta "$ q.defer") nel servizio.
dndr,

1
L'iniezione avviene nella funzione di livello superiore passata a .service, quindi spostati $qe $httplì.
Josh David Miller,

1
@XMLilley La stringa in una risoluzione è in realtà il nome di un servizio e non una particolare funzione sul servizio. Usando il tuo esempio, potresti farlo pageData: 'myData', ma dovresti quindi chiamare pageData.overviewdal tuo controller. Il metodo stringa è probabilmente utile solo se il factory di servizio ha restituito una promessa anziché un'API. Quindi il modo in cui lo stai facendo attualmente è probabilmente il modo migliore.
Josh David Miller,

2
@BrianVanderbusch Devo ammettere un po 'di confusione su dove esattamente pensi che non siamo d'accordo. Il vero problema riscontrato dall'OP era che iniettava un servizio in un blocco di configurazione, il che non può essere fatto . La soluzione è iniettare il servizio nella risoluzione . Mentre la tua risposta ha fornito molti dettagli sulla configurazione del servizio, non vedo come si riferisse in alcun modo all'errore che l'OP ha riscontrato e la tua soluzione al problema dell'OP era esattamente la stessa : hai iniettato il servizio nella funzione di risoluzione e non la funzione di configurazione. Puoi approfondire dove non siamo d'accordo qui?
Josh David Miller,

1
@JoshDavidMiller Utilizzando il metodo che ho dimostrato, è possibile configurare un servizio prima dell'attivazione dello stato, in modo tale che gli errori possano essere generati / gestiti durante la fase di configurazione, alterando potenzialmente il modo in cui si creerebbe un'istanza di altri valori di configurazione prima del bootstrap dell'applicazione. Ad esempio, determinare il ruolo di un utente per fare in modo che l'applicazione compili le giuste funzionalità.
Brian Vanderbusch,

140

Configura il tuo servizio come fornitore AngularJS personalizzato

Nonostante ciò che dice la risposta accettata, in realtà PUOI fare quello che avevi intenzione di fare, ma devi impostarlo come provider configurabile, in modo che sia disponibile come servizio durante la fase di configurazione. Innanzitutto, cambia il tuo Servicein un provider come mostrato di seguito. La differenza chiave qui è che dopo aver impostato il valore di defer, si imposta la defer.promiseproprietà sull'oggetto promessa restituito da $http.get:

Servizio provider: (fornitore: ricetta di servizio)

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

Ora hai un Provider personalizzato configurabile, devi solo iniettarlo. La differenza chiave qui è il mancante "Provider sul tuo iniettabile".

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

usa i dati risolti nel tuo 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);
     }

})

Possibili alternative

La seguente alternativa è un approccio simile, ma consente la definizione all'interno del .config, incapsulando il servizio all'interno del modulo specifico nel contesto della tua app. Scegli il metodo che fa per te. Vedi anche sotto per le note su una terza alternativa e link utili per aiutarti a capire come funzionano tutte queste cose

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

Alcune risorse utili

  • John Lindquist ha un'eccellente spiegazione e dimostrazione di 5 minuti su egghead.io ed è una delle lezioni gratuite! Ho sostanzialmente modificato la sua dimostrazione rendendola $httpspecifica nel contesto di questa richiesta
  • Visualizza la guida per gli sviluppatori di AngularJS sui provider
  • C'è anche un'eccellente spiegazione su factory/ service/ provider su clevertech.biz .

Il provider ti offre un po 'più di configurazione sul .servicemetodo, il che lo rende migliore come provider a livello di applicazione, ma potresti anche incapsularlo all'interno dell'oggetto stesso $provideconfig iniettando in config in questo modo:


2
Grazie, stavo cercando un esempio del genere; risposta dettagliata e ottimi collegamenti!
cnlevy,

1
nessun problema! Questa è la mia risposta preferita su SO. Ho risposto a questa domanda quando la risposta attualmente accettata aveva già ricevuto risposta e ottenuto 18 voti. Buono per alcuni badge!
Brian Vanderbusch,

Sarebbe davvero utile se i tuoi campioni di codepen funzionassero. Ad esempio, $ fornire.service ('dbService', function () {non ha $ http iniettato ma lo usa nel suo corpo. Allo stato attuale, non sono riuscito a far funzionare la tua tecnica 2. È molto frustrante che sia così difficile caricare i dati di configurazione da un file di rimozione in un programma angolare all'avvio.
Bernard

@Alkaline Ho imparato una cosa o due da questo post. La risposta è corretta in teoria ma ha 1 o 2 cose (1 che hai sottolineato) che dovrebbero essere risolte. Grazie per il commento. Esaminerò e aggiornerò la risposta. Ho eliminato il codice per il momento ... non ho mai avuto la possibilità di finirlo.
Brian Vanderbusch,

5
Penso che le informazioni fornite qui non siano corrette. Puoi usare un provider ma durante la fase di configurazione non stai lavorando con il risultato della $getchiamata. Invece si desidera aggiungere metodi sull'istanza del provider e tornare thisquando si chiama $get. In effetti nel tuo esempio potresti semplicemente usare un servizio ... In un provider non puoi nemmeno iniettare servizi come $http. E tra l'altro si //return the factory as a provider, that is available during the configuration phasetratta di informazioni fuorvianti / errate
Dieterg

21

Risposta breve: non puoi. AngularJS non ti permetterà di iniettare servizi nella configurazione perché non può essere sicuro che siano stati caricati correttamente.

Vedi questa domanda e risposta: iniezione di dipendenza AngularJS di valore all'interno di module.config

Un modulo è una raccolta di blocchi di configurazione ed esecuzione che vengono applicati all'applicazione durante il processo di bootstrap. Nella sua forma più semplice il modulo consiste nella raccolta di due tipi di blocchi:

Blocchi di configurazione : vengono eseguiti durante le registrazioni del provider e la fase di configurazione. Solo i provider e le costanti possono essere iniettati nei blocchi di configurazione. Questo serve a prevenire l'istanza accidentale dei servizi prima che siano stati completamente configurati.


2
in realtà, questo può essere fatto. Fornire una risposta a breve spiegando.
Brian Vanderbusch,

5

Non credo che dovresti essere in grado di farlo, ma ho iniettato con successo un servizio in un configblocco. (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();
                    }
                });
            };
        }
    ]);

il nome del metodo di servizio è dogmaCacheBuster ma in .configte hai scritto cacheBuster (che non è definito da nessuna parte nella risposta) e dogmaCacheBusterProvider (che non viene più utilizzato). Chiarirai su questo?
diEcho,

@ pro.mean Penso che stavo dimostrando la tecnica che ho usato, per inserire un servizio in un blocco di configurazione, ma è passato un po 'di tempo. cacheBusterè definito come parametro della funzione di configurazione. Per quanto riguarda dogmaCacheBusterProvider, è qualcosa di intelligente che Angular fa con le convenzioni di denominazione, che ho da tempo dimenticato. Questo potrebbe avvicinarti, stackoverflow.com/a/20881705/110010 .
kim3er,

su questo un altro riferimento. Conosco quel Provider di appendice qualunque cosa definiamo nella .provider()ricetta. cosa succede se definisco qualcosa con .factory('ServiceName')o la .service('ServiceName')ricetta e voglio usare uno dei suoi metodi in .config blocco, impostare il parametro come ServiceNameProvider ma interrompe la mia applicazione.
diEcho,

5

È possibile utilizzare $ inject service per iniettare un servizio nella propria configurazione

app.config (function ($ forniscono) {

    $ provid.decorator ("$ exceptionHandler", funzione ($ delegate, $ injector) {
        funzione di ritorno (eccezione, causa) {
            var $ rootScope = $ injector.get ("$ rootScope");
            $ rootScope.addError ({messaggio: "Eccezione", motivo: eccezione});
            $ delegate (eccezione, causa);
        };
    });

});

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


5

** Richiedi esplicitamente servizi da altri moduli utilizzando angular.injector **

Solo per elaborare la risposta di Kim3er , puoi fornire servizi, fabbriche, ecc. Senza cambiarli in provider, purché siano inclusi in altri moduli ...

Tuttavia, non sono sicuro se il *Provider(che viene creato internamente da angolare dopo che elabora un servizio o una fabbrica) sarà sempre disponibile (potrebbe dipendere da cosa è stato caricato per primo), poiché i moduli angolari caricano pigramente.

Si noti che se si desidera reiniettare i valori che devono essere trattati come costanti.

Ecco un modo più esplicito e probabilmente più affidabile per farlo + un plunker funzionante

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

2

Utilizzo di $ injector per chiamare i metodi di servizio in config

Ho avuto un problema simile e l'ho risolto utilizzando il servizio $ injector come mostrato sopra. Ho provato a iniettare direttamente il servizio ma ho finito con una dipendenza circolare da $ http. Il servizio visualizza un modale con l'errore e sto usando ui-bootstrap modale che ha anche una dipendenza da $ 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.")
            }
        }
    }

Grazie che ha aiutato molto
Erez,

2

Una soluzione molto semplice da fare

Nota : è solo per una chiamata asincrone, poiché il servizio non è inizializzato durante l'esecuzione della configurazione.

Puoi usare il run()metodo. Esempio :

  1. Il tuo servizio si chiama "MyService"
  2. Vuoi usarlo per un'esecuzione asincrone su un provider "MyProvider"

Il tuo codice :

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

1

Bene, ho faticato un po 'con questo, ma in realtà l'ho fatto.

Non so se le risposte siano obsolete a causa di alcuni cambiamenti angolari, ma puoi farlo in questo modo:

Questo è il tuo servizio:

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

E questa è la configurazione

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

  resolve: {
    beer: function(beerRetrievalService) {
      return beerRetrievalService.getRandomBeer();
    }
  }
})

0

Il modo più semplice: $injector = angular.element(document.body).injector()

Quindi usalo per eseguire invoke()oget()


Che hack! Sfortunatamente, non funzionerà con la maggior parte dei test unitari in cui l'applicazione non è legata al DOM.
rixo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.