Injetar serviço no app.config

168

Desejo injetar um serviço no app.config, para que os dados possam ser recuperados antes que o controlador seja chamado. Eu tentei assim:

Serviço:

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

Mas eu recebo este erro:

Erro: Fornecedor desconhecido: dbService do EditorApp

Como corrigir a instalação e injetar este serviço?

dndr
fonte
3
apesar do que você já vimos, não é uma maneira de conseguir o que você pretendia, e AngularJS passou muito tempo permitindo este tipo de funcionalidade. Revise minha resposta sobre como conseguir isso.
Brian Vanderbusch

Respostas:

131

Alex forneceu o motivo correto para não conseguir fazer o que você está tentando fazer, então marque com +1. Mas você está enfrentando esse problema porque não está usando bem resolve como eles são projetados.

resolvepega a sequência de um serviço ou uma função retornando um valor a ser injetado. Como você está fazendo o último, você precisa passar uma função real:

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

Quando a estrutura resolve data, ela injeta dbServicea função na função, para que você possa usá-la livremente. Você não precisa injetar no configbloco para conseguir isso.

Bom apetite!

Josh David Miller
fonte
2
Obrigado! No entanto, se eu fizer isso, recebo: Erro: 'indefinido' não é um objeto (avaliando '$ q.defer') no serviço.
DNDR
1
A injecção ocorre na função de nível superior passado para .service, por isso, mover $qe $httplá.
Josh David Miller
1
@XMLilley A string em uma resolução é na verdade o nome de um serviço e não uma função específica no serviço. Usando seu exemplo, você poderia fazer isso pageData: 'myData', mas seria necessário chamar pageData.overviewdo seu controlador. O método string provavelmente será útil apenas se a fábrica de serviços retornou uma promessa em vez de uma API. Portanto, a maneira como você está fazendo isso provavelmente é a melhor.
Josh David Miller
2
@BrianVanderbusch Devo admitir que há alguma confusão sobre exatamente onde você acha que discordamos. O problema real encontrado pelo OP foi que ele injetou um serviço em um bloco de configuração, o que não pode ser feito . A solução é injetar o serviço na resolução . Embora sua resposta tenha fornecido muitos detalhes sobre a configuração do serviço, não vejo como ela se relacionou de alguma forma com o erro encontrado pelo OP e sua solução para o problema do OP foi exatamente a mesma : você injetou o serviço na função de resolução e não é a função de configuração. Você pode elaborar onde discordamos aqui?
Josh David Miller
1
@JoshDavidMiller Usando o método que demonstrei, é possível configurar um serviço antes da ativação do estado, para que erros possam ser gerados / manipulados durante a fase de configuração, alterando potencialmente como você instanciaria outros valores de configuração antes da inicialização do aplicativo. Por exemplo, determinar a função de um usuário para que o aplicativo compile os recursos corretos.
precisa saber é o seguinte
140

Configure seu serviço como um provedor AngularJS personalizado

Apesar do que a resposta Aceita diz, você realmente PODE fazer o que pretendia fazer, mas precisa configurá-lo como um provedor configurável, para que fique disponível como um serviço durante a fase de configuração. Primeiro, mude Servicepara um provedor como mostrado abaixo. A principal diferença aqui é que, após definir o valor de defer, você define a defer.promisepropriedade para o objeto de promessa retornado por $http.get:

Serviço do provedor: (provedor: receita de serviço)

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

Agora, você tem um provedor personalizado configurável, basta injetá-lo. A principal diferença aqui é o "Fornecedor do seu injetável" ausente.

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

use dados resolvidos em seu 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);
     }

})

Alternativas possíveis

A alternativa a seguir é uma abordagem semelhante, mas permite que a definição ocorra dentro de .config, encapsulando o serviço para dentro do módulo específico no contexto do seu aplicativo. Escolha o método certo para você. Veja também abaixo as notas sobre um terceiro link alternativo e útil para ajudá-lo a entender tudo isso

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

Alguns recursos úteis

  • John Lindquist tem uma excelente explicação e demonstração de 5 minutos no egghead.io e é uma das lições gratuitas! Modifiquei basicamente sua demonstração, $httpespecificando-a no contexto desta solicitação
  • Veja o guia do AngularJS Developer em Provedores
  • Há também uma excelente explicação sobre factory/ service/ provider no clevertech.biz .

O provedor fornece um pouco mais de configuração sobre o .servicemétodo, o que o torna melhor como provedor de nível de aplicativo, mas você também pode encapsular isso no próprio objeto de configuração, injetando o $provideconfig da seguinte maneira:

Brian Vanderbusch
fonte
2
Obrigado, eu estava procurando por um exemplo; resposta detalhada e ótimos links!
cnlevy
1
sem problemas! Esta é a minha resposta favorita no SO. Eu respondi quando a resposta atualmente aceita já havia sido respondida e tinha 18 votos. Bom para alguns emblemas!
Brian Vanderbusch
Seria realmente útil se suas amostras de codepen funcionassem. Por exemplo, $ deliver.service ('dbService', function () {não tem $ http injetado, mas o usa em seu corpo. Como está, não consegui que sua técnica 2 funcionasse. É muito frustrante que seja tão difícil de carregar dados de configuração de um arquivo de remoção de um programa angular na inicialização.
Bernard

@ Alcaline Eu aprendi uma coisa ou duas desde este post. A resposta está correta na teoria, mas tem 1 ou 2 coisas (1 você apontou) que devem ser corrigidas. Obrigado pelo comentário. Vou revisar e atualizar a resposta. Excluído o codepen no momento ... nunca tive a chance de finalizá-lo.
Brian Vanderbusch

5
Acho que as informações que você forneceu aqui estão incorretas. Você pode usar um provedor, mas durante a fase de configuração, você não está trabalhando com o resultado da $getchamada. Em vez disso, você deseja adicionar métodos à instância do provedor e retornar thisquando ligar $get. De fato, no seu exemplo, você pode simplesmente usar um serviço ... Em um provedor, você também não pode injetar serviços como $http. E btw este //return the factory as a provider, that is available during the configuration phaseé enganosa / informações incorretas
Dieterg

21

Resposta curta: você não pode. O AngularJS não permitirá que você injete serviços na configuração porque não pode ter certeza de que eles foram carregados corretamente.

Consulte esta pergunta e resposta: Injeção de valor AngularJS dentro do module.config

Um módulo é uma coleção de blocos de configuração e execução que são aplicados ao aplicativo durante o processo de inicialização. Na sua forma mais simples, o módulo consiste na coleta de dois tipos de blocos:

Blocos de configuração - são executados durante os registros do provedor e a fase de configuração. Somente provedores e constantes podem ser injetados em blocos de configuração. Isso evita a instanciação acidental de serviços antes de serem totalmente configurados.


2
na verdade, isso pode ser feito. Fornecendo uma resposta em breve explicando.
Brian Vanderbusch

5

Não acho que você consiga fazer isso, mas injetei com êxito um serviço em um configbloco. (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();
                    }
                });
            };
        }
    ]);

O nome do método de serviço é dogmaCacheBuster, mas .configvocê escreveu cacheBuster (que não está definido em nenhum lugar da resposta) e dogmaCacheBusterProvider (que não é usado mais). Você vai esclarecer isso?
diEcho 14/06

@ pro.mean Acho que estava demonstrando a técnica que usei para inserir um serviço em um bloco de configuração, mas já faz um tempo. cacheBusteré definido como um parâmetro da função de configuração. Com relação a dogmaCacheBusterProvider, é algo inteligente que Angular faz com convenções de nomenclatura, que eu esqueci há muito tempo. Isso pode aproximá-lo, stackoverflow.com/a/20881705/110010 .
Kim3er

nesta outra referência. Eu conheço esse acréscimo de Provedor, seja o que for que definimos na .provider()receita. e se eu definir algo com .factory('ServiceName')ou .service('ServiceName')receita e quiser usar um de seus métodos em .config bloco, defina o parâmetro como ServiceNameProvider, mas ele interromperá meu aplicativo.
diEcho 15/06

5

Você pode usar o serviço $ inject para injetar um serviço na sua configuração

app.config (função ($ fornecer) {

    $ allow.decorator ("$ exceptionHandler", função ($ delegate, $ injector) {
        função de retorno (exceção, causa) {
            var $ rootScope = $ injector.get ("$ rootScope");
            $ rootScope.addError ({message: "Exception", motivo: exceção});
            $ delegate (exceção, causa);
        };
    });

});

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

Koray Güclü
Obrigado que ajudou muito
Erez 29/06
Que truque! Infelizmente, ele não funcionará com a maioria dos testes de unidade em que o aplicativo não está vinculado ao DOM.
rixo
5

** Solicite explicitamente serviços de outros módulos usando angular.injector **

Apenas para elaborar a resposta do kim3er , você pode fornecer serviços, fábricas, etc., sem alterá-los para provedores, desde que incluídos em outros módulos ...

No entanto, não tenho certeza se o *Provider(que é feito internamente pela angular após o processamento de um serviço ou fábrica) estará sempre disponível (pode depender do que mais foi carregado primeiro), pois o angular carrega preguiçosamente os módulos.

Observe que se você deseja injetar novamente os valores, eles devem ser tratados como constantes.

Aqui está uma maneira mais explícita e provavelmente mais confiável de fazer isso + um desentupidor funcional

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

Usando $ injector para chamar métodos de serviço em config

Eu tive um problema semelhante e o resolvi usando o serviço $ injector, como mostrado acima. Tentei injetar o serviço diretamente, mas acabei com uma dependência circular de $ http. O serviço exibe um modal com o erro e estou usando o modal ui-bootstrap, que também 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
fonte
Obrigado que ajudou muito
Erez 29/06
2

Uma solução muito fácil de fazer

Nota : é apenas para uma chamada assíncrona, porque o serviço não é inicializado na execução da configuração.

Você pode usar o run()método Exemplo:

  1. Seu serviço é chamado "MyService"
  2. Você deseja usá-lo para uma execução assíncrona em um provedor "MyProvider"

Seu código :

(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
fonte
1

Bem, lutei um pouco com esse, mas na verdade consegui.

Não sei se as respostas estão desatualizadas devido a alguma alteração na angular, mas você pode fazer o seguinte:

Este é o seu serviço:

.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 esta é a configuração

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

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

Caminho mais fácil: $injector = angular.element(document.body).injector()

Em seguida, use isso para executar invoke()ouget()

Pencilcheck
fonte
Que truque! Infelizmente, ele não funcionará com a maioria dos testes de unidade em que o aplicativo não está vinculado ao DOM.
rixo