Wstrzykuj usługę w app.config

168

Chcę wprowadzić usługę do pliku app.config, aby można było pobrać dane przed wywołaniem kontrolera. Spróbowałem tak:

Usługa:

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

Konfiguracja:

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

Ale pojawia się ten błąd:

Błąd: nieznany dostawca: dbService z EditorApp

Jak poprawić konfigurację i wstrzyknąć tę usługę?

dndr
źródło
3
pomimo tego, co już widzieliśmy, nie jest to sposób, aby osiągnąć to, co ma, i angularjs spędził dużo czasu na włączenie tego typu funkcjonalności. Przejrzyj moją odpowiedź, jak to osiągnąć.
Brian Vanderbusch

Odpowiedzi:

131

Alex podał prawidłowy powód, dla którego nie możesz zrobić tego, co próbujesz zrobić, więc +1. Ale napotykasz ten problem, ponieważ nie do końca używasz rozwiązań, w jaki sposób są zaprojektowane.

resolvepobiera ciąg usługi lub funkcję zwracającą wartość do wstrzyknięcia. Ponieważ robisz to drugie, musisz przekazać rzeczywistą funkcję:

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

Kiedy framework zostanie rozwiązany data, wstrzyknie element dbServicedo funkcji, dzięki czemu możesz go swobodnie używać. Nie musisz w ogóle wstrzykiwać do configbloku, aby to osiągnąć.

Smacznego!

Josh David Miller
źródło
2
Dzięki! Jeśli jednak to zrobię, otrzymam: Błąd: „undefined” nie jest obiektem (oceniającym „$ q.defer”) w usłudze.
dndr
1
Wstrzyknięcie następuje w funkcji najwyższego poziomu przekazanej do .service, więc ruszaj $qi $httptam.
Josh David Miller,
1
@XMLilley Ciąg w rozwiązaniu to w rzeczywistości nazwa usługi, a nie konkretna funkcja w usłudze. Korzystając z twojego przykładu, mógłbyś to zrobić pageData: 'myData', ale wtedy musiałbyś zadzwonić pageData.overviewze swojego kontrolera. Metoda string jest prawdopodobnie użyteczna tylko wtedy, gdy fabryka usług zwróciła obietnicę zamiast interfejsu API. Tak więc sposób, w jaki obecnie to robisz, jest prawdopodobnie najlepszym sposobem.
Josh David Miller,
2
@BrianVanderbusch Muszę przyznać, że nie zgadzamy się z tym, gdzie dokładnie czujesz. Rzeczywisty problem napotkany przez OP polegał na tym, że wstrzyknął usługę do bloku konfiguracyjnego, czego nie można zrobić . Rozwiązaniem jest wstrzyknięcie usługi do rozwiązania . Chociaż twoja odpowiedź zawierała wiele szczegółów na temat konfiguracji usługi, nie widzę, jak to się w jakikolwiek sposób wiązało z błędem napotkanym przez OP, a twoje rozwiązanie problemu OP było dokładnie takie samo : wstrzyknąłeś usługę w funkcję rozwiązywania i nie funkcja config. Czy możesz wyjaśnić, gdzie się tutaj nie zgadzamy?
Josh David Miller
1
@JoshDavidMiller Korzystając z metody, którą przedstawiłem, możliwe jest skonfigurowanie usługi przed aktywacją stanu, tak że błędy mogą być zgłaszane / obsługiwane podczas fazy konfiguracji, potencjalnie zmieniając sposób tworzenia instancji innych wartości konfiguracyjnych przed ładowaniem aplikacji. Na przykład określenie roli użytkownika, aby aplikacja mogła skompilować odpowiednie funkcje.
Brian Vanderbusch
140

Skonfiguruj usługę jako niestandardowego dostawcę AngularJS

Pomimo tego, co mówi Zaakceptowana odpowiedź, w rzeczywistości MOŻESZ zrobić to, co zamierzałeś zrobić, ale musisz ustawić go jako konfigurowalnego dostawcę, aby był dostępny jako usługa na etapie konfiguracji. Najpierw zmień Servicedostawcę na dostawcę jak pokazano niżej. Kluczowa różnica polega na tym, że po ustawieniu wartości deferustawia się defer.promisewłaściwość na obiekt obietnicy zwracany przez $http.get:

Usługa dostawcy: (dostawca: przepis na usługę)

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

Teraz masz konfigurowalnego dostawcę niestandardowego, wystarczy go wstrzyknąć. Kluczowa różnica polega na tym, że brakujący „Dostawca na Twoim wstrzykiwaczu”.

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

użyj rozwiązanych danych w swoim 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);
     }

})

Możliwe alternatywy

Poniższa alternatywa jest podobnym podejściem, ale umożliwia definicję w ramach .config, hermetyzowanie usługi do określonego modułu w kontekście aplikacji. Wybierz odpowiednią dla siebie metodę. Zobacz także poniżej uwagi na temat trzeciej alternatywy i pomocnych linków, które pomogą Ci zrozumieć te wszystkie rzeczy

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

Kilka pomocnych zasobów

  • John Lindquist ma doskonałe 5-minutowe wyjaśnienie i demonstrację tego na egghead.io i jest to jedna z bezpłatnych lekcji! Zasadniczo zmodyfikowałem jego demonstrację, czyniąc ją $httpkonkretną w kontekście tej prośby
  • Zobacz przewodnik dla programistów AngularJS na temat dostawców
  • Na clevertech.biz znajduje się również doskonałe wyjaśnienie dotyczące factory/ service/ .provider

Dostawca zapewnia nieco większą konfigurację w stosunku do .servicemetody, co czyni go lepszym jako dostawca na poziomie aplikacji, ale możesz również hermetyzować to w samym obiekcie konfiguracyjnym, wstrzykując $providedo konfiguracji w następujący sposób:

Brian Vanderbusch
źródło
2
Dziękuję, szukałem takiego przykładu; szczegółowa odpowiedź i świetne linki!
cnlevy
1
nie ma problemu! To moja ulubiona odpowiedź na SO. Odpowiedziałem na to, gdy na aktualnie zaakceptowaną odpowiedź została już udzielona odpowiedź i miałem 18 głosów. Dobre dla kilku odznak!
Brian Vanderbusch,
Byłoby naprawdę przydatne, gdyby próbki codepen działały. Na przykład $ provider.service ('dbService', function () {nie ma wstrzykniętego $ http, ale używa go w swoim treści. W obecnej sytuacji nie mogłem uruchomić Twojej techniki 2. To bardzo frustrujące, że jest tak trudno załadować dane konfiguracyjne z usuwanego pliku w programie Angular przy starcie.
Bernard

@Alkaline Od czasu tego postu nauczyłem się kilku rzeczy. Odpowiedź jest poprawna w teorii, ale zawiera 1 lub 2 rzeczy (1 wskazałeś), które należy naprawić. Dziękuję za komentarz. Sprawdzę i zaktualizuję odpowiedź. Usunąłem na razie kodepen ... nigdy nie miałem okazji go ukończyć.
Brian Vanderbusch

5
Uważam, że podane przez Ciebie informacje są nieprawidłowe. Możesz użyć dostawcy, ale w fazie konfiguracji nie pracujesz z wynikiem $getpołączenia. Zamiast tego chcesz dodać metody do wystąpienia dostawcy i po prostu powrócić thispodczas wywołania $get. W rzeczywistości w twoim przykładzie możesz po prostu użyć usługi ... W dostawcy nie możesz również wstrzyknąć usług takich jak $http. A przy okazji są to //return the factory as a provider, that is available during the configuration phasemylące / niepoprawne informacje
Dieterg

21

Krótka odpowiedź: nie możesz. AngularJS nie pozwoli ci wstrzyknąć usług do konfiguracji, ponieważ nie może być pewien, że zostały poprawnie załadowane.

Zobacz to pytanie i odpowiedź: Wstrzyknięcie wartości w zależności od AngularJS wewnątrz module.config

Moduł to zbiór bloków konfiguracyjnych i uruchomieniowych, które są stosowane do aplikacji podczas procesu ładowania początkowego. W swojej najprostszej formie moduł składa się ze zbioru dwóch rodzajów bloków:

Bloki konfiguracyjne - są wykonywane podczas rejestracji dostawców i fazy konfiguracji. Do bloków konfiguracyjnych można wstrzykiwać tylko dostawców i stałe. Ma to na celu zapobieżenie przypadkowemu wystąpieniu usług przed ich pełną konfiguracją.


2
w rzeczywistości można to zrobić. Udzielenie odpowiedzi z krótkim wyjaśnieniem.
Brian Vanderbusch

5

Nie sądzę, abyś był w stanie to zrobić, ale pomyślnie wstrzyknąłem usługę do configbloku. (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();
                    }
                });
            };
        }
    ]);

nazwa metody usługi to dogmaCacheBuster, ale .confignapisałeś cacheBuster (który nie jest zdefiniowany nigdzie w odpowiedzi) i dogmaCacheBusterProvider (który nie jest dalej używany). Czy możesz to wyjaśnić?
diEcho

@ pro.mean Myślę, że demonstrowałem technikę, której użyłem, aby wstawić usługę do bloku konfiguracyjnego, ale minęło trochę czasu. cacheBusterjest zdefiniowany jako parametr funkcji config. Jeśli chodzi o dogmaCacheBusterProviderto, to coś sprytnego, co Angular robi z konwencjami nazewnictwa, o których dawno zapomniałem. To może Cię przybliżyć, stackoverflow.com/a/20881705/110010 .
kim3er

na ten inny odnośnik. Dowiedziałem się, że dołączaj Dostawcę, cokolwiek zdefiniujemy w .provider()przepisie. co jeśli zdefiniuję coś za pomocą .factory('ServiceName')lub .service('ServiceName')Receptę i chcę użyć jednej z jej metod w .config bloku, ustaw parametr jako ServiceNameProvider, ale zatrzyma moją aplikację.
diEcho

5

Możesz użyć usługi $ inject, aby wstrzyknąć usługę w swojej konfiguracji

app.config (function ($ provider) {

    $ provider.decorator ("$ wyjątekHandler", function ($ delegate, $ injector) {
        funkcja powrotu (wyjątek, przyczyna) {
            var $ rootScope = $ injector.get ("$ rootScope");
            $ rootScope.addError ({wiadomość: "Wyjątek", przyczyna: wyjątek});
            $ delegate (wyjątek, przyczyna);
        };
    });

});

Źródło: http://odetocode.com/blogs/scott/archive/2014/04/21/better-error-handling-in-angularjs.aspx

Koray Güclü
Dzięki, że bardzo pomogło
Erez
Co za hack! Niestety, nie będzie działać z większością testów jednostkowych, w których aplikacja nie jest powiązana z DOM.
rixo
5

** Wyraźnie żądaj usług z innych modułów za pomocą angular.injector **

Aby rozwinąć odpowiedź kim3er , możesz świadczyć usługi, fabryki itp. Bez zmiany ich na dostawców, o ile są one zawarte w innych modułach ...

Nie jestem jednak pewien, czy *Provider(który jest wykonany wewnętrznie przez angular po przetworzeniu usługi lub fabryki) zawsze będzie dostępny (może zależeć od tego, co jeszcze zostanie załadowane jako pierwsze), ponieważ angular leniwie ładuje moduły.

Zwróć uwagę, że jeśli chcesz ponownie wstrzyknąć wartości, powinny być traktowane jako stałe.

Oto bardziej wyraźny i prawdopodobnie bardziej niezawodny sposób na zrobienie tego + działający 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
źródło
2

Używanie $ injector do wywoływania metod serwisowych w config

Miałem podobny problem i rozwiązałem go za pomocą usługi $ injector, jak pokazano powyżej. Próbowałem bezpośrednio wstrzyknąć usługę, ale skończyło się na cyklicznej zależności od $ http. Usługa wyświetla modal z błędem i używam modalu ui-bootstrap, który również ma zależność od $ 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
źródło
Dzięki, że bardzo pomogło
Erez
2

Rozwiązanie bardzo łatwe do zrobienia

Uwaga : jest to tylko dla wywołania asynchronicznego, ponieważ usługa nie jest inicjowana podczas wykonywania konfiguracji.

Możesz użyć run()metody. Przykład:

  1. Twoja usługa nazywa się „MyService”
  2. Chcesz go użyć do wykonania asynchronicznego na dostawcy „MyProvider”

Twój kod :

(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
źródło
1

Cóż, trochę się zmagałem z tym, ale faktycznie to zrobiłem.

Nie wiem, czy odpowiedzi są nieaktualne z powodu jakiejś zmiany kąta, ale możesz to zrobić w ten sposób:

To jest twoja usługa:

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

A to jest config

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

  resolve: {
    beer: function(beerRetrievalService) {
      return beerRetrievalService.getRandomBeer();
    }
  }
})
Luis Sieira
źródło
0

Najprostszy sposób: $injector = angular.element(document.body).injector()

Następnie użyj tego do uruchomienia invoke()lubget()

Pencilcheck
źródło
Co za hack! Niestety, nie będzie działać z większością testów jednostkowych, w których aplikacja nie jest powiązana z DOM.
rixo