Injizieren Sie den Dienst in app.config

168

Ich möchte einen Dienst in app.config einfügen, damit Daten abgerufen werden können, bevor der Controller aufgerufen wird. Ich habe es so versucht:

Bedienung:

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

Konfiguration:

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

Aber ich bekomme diesen Fehler:

Fehler: Unbekannter Anbieter: dbService von EditorApp

Wie korrigiere ich das Setup und injiziere diesen Service?

dndr
quelle
3
Trotz allem, was Sie bereits gesehen haben, gibt es eine Möglichkeit, das zu erreichen, was Sie beabsichtigt haben, und AngularJS hat viel Zeit damit verbracht, diese Art von Funktionalität zu aktivieren. Überprüfen Sie meine Antwort, wie dies erreicht werden kann.
Brian Vanderbusch

Antworten:

131

Alex hat den richtigen Grund angegeben, warum er nicht in der Lage ist, das zu tun, was Sie versuchen, also +1. Dieses Problem tritt jedoch auf, weil Sie nicht genau festlegen, wie sie entworfen wurden.

resolveNimmt entweder die Zeichenfolge eines Dienstes oder eine Funktion, die einen zu injizierenden Wert zurückgibt. Da Sie Letzteres tun, müssen Sie eine tatsächliche Funktion übergeben:

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

Wenn das Framework aufgelöst wird, datawird es dbServicein die Funktion eingefügt , sodass Sie es frei verwenden können. Sie müssen überhaupt nicht in den configBlock injizieren , um dies zu erreichen.

Guten Appetit!

Josh David Miller
quelle
2
Vielen Dank! Wenn ich dies tue, erhalte ich jedoch Folgendes: Fehler: 'undefined' ist kein Objekt (Auswertung von '$ q.defer') im Dienst.
dndr
1
Die Injektion erfolgt in der Top-Level - Funktion übergeben .service, so bewegen $qund $httpdort.
Josh David Miller
1
@XMLilley Die Zeichenfolge in einer Auflösung ist eigentlich der Name eines Dienstes und keine bestimmte Funktion des Dienstes. Anhand Ihres Beispiels könnten Sie dies tun pageData: 'myData', müssten dann aber pageData.overviewvon Ihrem Controller aus anrufen . Die Zeichenfolgenmethode ist wahrscheinlich nur dann nützlich, wenn die Service Factory anstelle einer API ein Versprechen zurückgegeben hat. Die Art und Weise, wie Sie es derzeit tun, ist wahrscheinlich die beste.
Josh David Miller
2
@BrianVanderbusch Ich muss zugeben, dass wir uns nicht sicher sind, wo genau Sie der Meinung sind, dass wir nicht einverstanden sind. Das eigentliche Problem, auf das das OP stieß, war, dass er einen Dienst in einen Konfigurationsblock eingefügt hat, was nicht möglich ist . Die Lösung besteht darin, den Dienst in die Lösung einzufügen . Obwohl Ihre Antwort viele Details zur Dienstkonfiguration enthielt, sehe ich keinen Zusammenhang mit dem Fehler, auf den das OP gestoßen ist, und Ihre Lösung für das Problem des OP war genau dieselbe : Sie haben den Dienst in die Auflösungsfunktion und eingefügt nicht die Konfigurationsfunktion. Können Sie näher erläutern, wo wir hier nicht einverstanden sind?
Josh David Miller
1
@JoshDavidMiller Mit der von mir demonstrierten Methode ist es möglich, einen Dienst vor der Statusaktivierung so zu konfigurieren, dass Fehler während der Konfigurationsphase ausgelöst / behandelt werden können, wodurch möglicherweise geändert wird, wie Sie andere Konfigurationswerte vor dem Bootstrapping der Anwendung instanziieren würden. Bestimmen Sie beispielsweise die Rolle eines Benutzers, damit die Anwendung die richtigen Funktionen kompiliert.
Brian Vanderbusch
140

Richten Sie Ihren Dienst als benutzerdefinierten AngularJS-Anbieter ein

Trotz allem , was die akzeptierte Antwort sagt, Sie tatsächlich CAN tun , was Sie zu tun beabsichtigen, aber Sie müssen es einrichten als konfigurierbares Anbieter, so dass es während der Konfigurationsphase als Dienst verfügbar ist .. Zuerst Ihre ändern Servicezu einem Anbieter Wie nachfolgend dargestellt. Der Hauptunterschied besteht darin, dass Sie nach dem Festlegen des Werts von deferdie defer.promiseEigenschaft auf das Versprechenobjekt setzen , das zurückgegeben wird von $http.get:

Provider Service: (Provider: Servicerezept)

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

Jetzt haben Sie einen konfigurierbaren benutzerdefinierten Anbieter, den Sie nur noch injizieren müssen. Der Hauptunterschied ist hier der fehlende "Provider on your injizable".

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

Verwenden Sie aufgelöste Daten in Ihrem 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);
     }

})

Mögliche Alternativen

Die folgende Alternative ist ein ähnlicher Ansatz, ermöglicht jedoch die Definition innerhalb von .configund kapselt den Dienst innerhalb des spezifischen Moduls im Kontext Ihrer App. Wählen Sie die für Sie passende Methode. Im Folgenden finden Sie Hinweise zu einer dritten Alternative und hilfreiche Links, mit denen Sie all diese Dinge besser verstehen können

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

Einige hilfreiche Ressourcen

  • John Lindquist hat eine hervorragende 5-minütige Erklärung und Demonstration auf Eierkopf.io , und es ist eine der kostenlosen Lektionen! Ich habe seine Demonstration grundlegend geändert, indem ich sie $httpim Kontext dieser Anfrage spezifisch gemacht habe
  • Lesen Sie das AngularJS-Entwicklerhandbuch zu Anbietern
  • Es gibt auch eine ausgezeichnete Erklärung zu factory/ service/ provider bei clevertech.biz .

Der Anbieter bietet Ihnen etwas mehr Konfiguration als die .serviceMethode, was sie als Anbieter auf Anwendungsebene besser macht. Sie können dies jedoch auch im Konfigurationsobjekt selbst kapseln, indem Sie $providewie folgt in die Konfiguration einfügen:

Brian Vanderbusch
quelle
2
Danke, ich habe nach einem solchen Beispiel gesucht; detaillierte Antwort und tolle Links!
Cnlevy
1
kein Problem! Dies ist meine Lieblingsantwort auf SO. Ich antwortete darauf, als die aktuell akzeptierte Antwort bereits beantwortet worden war und 18 Stimmen hatte. Gut für ein paar Abzeichen!
Brian Vanderbusch
Es wäre wirklich nützlich, wenn Ihre Codepen-Beispiele funktionieren würden. Zum Beispiel hat $ deploy.service ('dbService', function () {kein $ http injiziert, sondern verwendet es in seinem Körper. So wie es aussieht, konnte ich Ihre Technik 2 nicht zum Laufen bringen. Es ist sehr frustrierend, dass es so ist Es ist so schwer, Konfigurationsdaten aus einer entfernten Datei in einem Angular-Programm beim Start zu laden.
Bernard

@Alkaline Ich habe seit diesem Beitrag ein oder zwei Dinge gelernt. Die Antwort ist theoretisch richtig, hat aber 1 oder 2 Dinge (1, auf die Sie hingewiesen haben), die behoben werden sollten. Danke für den Kommentar. Ich werde die Antwort überprüfen und aktualisieren. Löschte den Codepen für den Moment ... hatte nie die Chance, ihn zu beenden.
Brian Vanderbusch

5
Ich denke, die Informationen, die Sie hier angegeben haben, sind falsch. Sie können einen Anbieter verwenden, aber während der Konfigurationsphase arbeiten Sie nicht mit dem Ergebnis des $getAnrufs. Stattdessen möchten Sie der Provider-Instanz Methoden hinzufügen und thisbeim Aufruf einfach zurückkehren $get. In Ihrem Beispiel könnten Sie sogar nur einen Dienst verwenden ... In einem Anbieter können Sie auch keine Dienste wie injizieren $http. Und übrigens ist dies //return the factory as a provider, that is available during the configuration phaseirreführende / falsche Informationen
Dieterg

21

Kurze Antwort: Sie können nicht. Mit AngularJS können Sie keine Dienste in die Konfiguration einfügen, da nicht sicher ist, ob sie korrekt geladen wurden.

Siehe diese Frage und Antwort: AngularJS-Abhängigkeitsinjektion von Wert innerhalb von module.config

Ein Modul ist eine Sammlung von Konfigurations- und Ausführungsblöcken, die während des Bootstrap-Prozesses auf die Anwendung angewendet werden. In seiner einfachsten Form besteht das Modul aus der Sammlung von zwei Arten von Blöcken:

Konfigurationsblöcke - werden während der Anbieterregistrierungs- und Konfigurationsphase ausgeführt. Nur Anbieter und Konstanten können in Konfigurationsblöcke eingefügt werden. Dies soll verhindern, dass Dienste versehentlich instanziiert werden, bevor sie vollständig konfiguriert wurden.


2
Eigentlich kann dies getan werden. Geben Sie eine Antwort, die kurz erklärt wird.
Brian Vanderbusch

5

Ich glaube nicht, dass Sie dazu in der Lage sein sollten, aber ich habe erfolgreich einen Dienst in einen configBlock eingefügt. (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();
                    }
                });
            };
        }
    ]);

Der Name der Dienstmethode lautet dogmaCacheBuster, aber .configSie haben cacheBuster (der an keiner Stelle in der Antwort definiert ist) und dogmaCacheBusterProvider (der nicht weiter verwendet wird) geschrieben. Wirst du das klären?
DiEcho

@ pro.mean Ich glaube, ich habe die Technik demonstriert, mit der ich einen Dienst in einen Konfigurationsblock eingefügt habe, aber es ist eine Weile her. cacheBusterwird als Parameter der Konfigurationsfunktion definiert. In Bezug auf dogmaCacheBusterProviderist es etwas Kluges, das Angular mit Namenskonventionen macht, das ich längst vergessen habe. Dies könnte Sie näher bringen, stackoverflow.com/a/20881705/110010 .
Kim3er

dazu eine weitere Referenz. Ich lerne diesen Anbieter kennen, was auch immer wir im .provider()Rezept definieren . Was passiert, wenn ich etwas mit .factory('ServiceName')oder .service('ServiceName')Rezept definiere und eine seiner Methoden im .config Block verwenden möchte ? Setzen Sie den Parameter als ServiceNameProvider, aber meine Anwendung wird gestoppt .
DiEcho

5

Sie können den $ injiz-Dienst verwenden, um einen Dienst in Ihre Konfiguration einzufügen

app.config (Funktion ($ bereitstellen) {

    $ liefern.decorator ("$ exceptionHandler", Funktion ($ delegate, $ injizier) {
        Rückgabefunktion (Ausnahme, Ursache) {
            var $ rootScope = $ injizor.get ("$ rootScope");
            $ rootScope.addError ({Nachricht: "Ausnahme", Grund: Ausnahme});
            $ delegate (Ausnahme, Ursache);
        };
    });

});

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

Koray Güclü
Vielen Dank, dass viel geholfen hat
Erez
Was für ein Hack! Leider funktioniert es bei den meisten Unit-Tests nicht, bei denen die Anwendung nicht an das DOM gebunden ist.
Rixo
5

** Fordern Sie mit angular.injector explizit Dienste von anderen Modulen an **

Um die Antwort von kim3er näher zu erläutern , können Sie Dienste, Fabriken usw. bereitstellen, ohne sie zu Anbietern zu ändern, sofern sie in anderen Modulen enthalten sind ...

Ich bin mir jedoch nicht sicher, ob das *Provider(das intern durch Winkel hergestellt wird, nachdem es einen Service oder eine Fabrik verarbeitet hat) immer verfügbar sein wird (es kann davon abhängen, was zuerst geladen wird), da Winkel Module träge lädt.

Beachten Sie, dass die Werte, wenn Sie sie erneut einfügen möchten, als Konstanten behandelt werden sollen.

Hier ist ein expliziterer und wahrscheinlich zuverlässigerer Weg, dies zu tun + ein funktionierender Plunker

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
quelle
2

Verwenden von $ insertor zum Aufrufen von Dienstmethoden in config

Ich hatte ein ähnliches Problem und löste es mithilfe des oben gezeigten $ injor-Dienstes. Ich habe versucht, den Dienst direkt zu injizieren, aber am Ende hatte ich eine zirkuläre Abhängigkeit von $ http. Der Dienst zeigt ein Modal mit dem Fehler an und ich verwende das UI-Bootstrap-Modal, das auch von $ https abhängig ist.

    $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
quelle
Vielen Dank, dass viel geholfen hat
Erez
2

Eine sehr einfache Lösung

Hinweis : Dies gilt nur für einen asynchronen Aufruf, da der Dienst bei der Ausführung der Konfiguration nicht initialisiert wird.

Sie können run()Methode verwenden. Beispiel:

  1. Ihr Dienst heißt "MyService"
  2. Sie möchten es für eine asynchrone Ausführung auf einem Anbieter "MyProvider" verwenden.

Dein 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
quelle
1

Nun, ich hatte ein wenig Probleme mit diesem, aber ich habe es tatsächlich getan.

Ich weiß nicht, ob die Antworten aufgrund einer Änderung des Winkels veraltet sind, aber Sie können dies folgendermaßen tun:

Dies ist Ihr 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;
    }
  };
 });

Und das ist die Konfiguration

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

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

Einfachster Weg: $injector = angular.element(document.body).injector()

Verwenden Sie das dann, um invoke()oder auszuführenget()

Bleistiftcheck
quelle
Was für ein Hack! Leider funktioniert es bei den meisten Unit-Tests nicht, bei denen die Anwendung nicht an das DOM gebunden ist.
Rixo