Внедрить сервис в app.config

168

Я хочу добавить сервис в app.config, чтобы данные могли быть получены до вызова контроллера. Я попробовал это так:

Обслуживание:

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

Но я получаю эту ошибку:

Ошибка: неизвестный поставщик: dbService из EditorApp

Как правильно настроить и внедрить этот сервис?

dndr
источник
3
несмотря на то , что вы уже видели, есть это способ добиться того, что вы хотели, и AngularJS потратил много времени , что позволяет этот тип функциональности. Просмотрите мой ответ о том, как этого добиться.
Брайан Вандербуш

Ответы:

131

Алекс указал правильную причину неспособности сделать то, что вы пытаетесь сделать, поэтому +1. Но вы столкнулись с этой проблемой, потому что вы не совсем используете, решает, как они разработаны.

resolveпринимает либо строку службы, либо функцию, возвращающую значение для внедрения. Поскольку вы делаете последнее, вам нужно передать фактическую функцию:

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

Когда фреймворк перейдет к разрешению data, он внедрит dbServiceв функцию, чтобы вы могли свободно ее использовать. Вам не нужно вводить в configблок вообще, чтобы выполнить это.

Приятного аппетита!

Джош Дэвид Миллер
источник
2
Спасибо! Однако, если я делаю это, я получаю: Ошибка: «undefined» не является объектом (оценивающим «$ q.defer») в сервисе.
'23
1
Инжекция происходит в переданной функции верхнего уровня .service, так что двигайтесь $qи $httpтуда.
Джош Дэвид Миллер
1
@XMLilley Строка в разрешении на самом деле является именем службы, а не конкретной функцией в службе. Используя ваш пример, вы можете это сделать pageData: 'myData', но тогда вам придется звонить pageData.overviewс вашего контроллера. Строковый метод, вероятно, полезен только в том случае, если фабрика служб возвратила обещание вместо API. Таким образом, то, как вы сейчас это делаете, вероятно, лучший способ.
Джош Дэвид Миллер
2
@BrianVanderbusch Я должен признаться в некоторой путанице в том, где именно вы чувствуете, что мы не согласны. Фактическая проблема, с которой столкнулся ОП, заключалась в том, что он внедрил службу в блок конфигурации, что невозможно сделать . Решение состоит в том, чтобы внедрить службу в решение . Хотя в вашем ответе содержится много подробностей о конфигурации службы, я не вижу, как она каким-либо образом связана с ошибкой, с которой столкнулся OP, и ваше решение проблемы OP было точно таким же : вы внедрили службу в функцию разрешения и не функция конфигурации. Можете ли вы уточнить, где мы здесь не согласны?
Джош Дэвид Миллер
1
@JoshDavidMiller Используя метод, который я продемонстрировал, можно настроить службу до активации состояния, так что ошибки могут быть выброшены / обработаны на этапе настройки, что может изменить то, как вы будете создавать другие значения конфигурации до начальной загрузки приложения. Например, определение роли пользователя, чтобы приложение компилировало нужные функции.
Брайан Вандербуш
140

Настройте свой сервис как пользовательский поставщик AngularJS

Несмотря на то, что говорится в ответе «Принято», вы на самом деле МОЖЕТЕ делать то, что намеревались сделать, но вам нужно настроить его в качестве настраиваемого поставщика, чтобы он был доступен в качестве службы на этапе настройки. Во-первых, измените Serviceего на поставщика. как показано ниже. Ключевым отличием здесь является то, что после установки значения deferвы устанавливаете defer.promiseсвойство для объекта обещания, возвращаемого $http.get:

Сервис провайдера: (провайдер: рецепт сервиса)

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

Теперь у вас есть настраиваемый пользовательский провайдер, вам просто нужно внедрить его. Ключевым отличием здесь является отсутствие «провайдера для инъекций».

конфигурации:

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

использовать разрешенные данные в вашем 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);
     }

})

Возможные альтернативы

Следующая альтернатива является аналогичным подходом, но позволяет определению происходить внутри .config, инкапсулируя службу в конкретном модуле в контексте вашего приложения. Выберите метод, который подходит именно вам. Также см. Ниже заметки о третьей альтернативе и полезные ссылки, которые помогут вам освоить все эти вещи

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

Несколько полезных ресурсов

  • У Джона Линдквиста есть отличное 5-минутное объяснение и демонстрация этого на egghead.io , и это один из бесплатных уроков! Я в основном изменил его демонстрацию, сделав его $httpконкретным в контексте этого запроса
  • Посмотреть руководство разработчика AngularJS по провайдерам
  • Существует также отличное объяснение о factory/ service/ provider на clevertech.biz .

Провайдер предоставляет вам немного больше настроек по сравнению с .serviceметодом, что делает его лучше в качестве провайдера уровня приложения, но вы также можете инкапсулировать это в самом объекте config, внедрив его $provideв config следующим образом:

Brian Vanderbusch
источник
2
Спасибо, я искал такой пример; подробный ответ и отличные ссылки!
cnlevy
1
без проблем! Это мой любимый ответ на SO. Я ответил на это, когда на текущий принятый ответ уже был дан ответ, и у него было 18 голосов. Хорошо для нескольких значков!
Брайан Вандербуш
Было бы очень полезно, если бы ваши примеры кода работали. Например, $ provide.service ('dbService', function () {не вводит $ http, но использует его в своем теле. В настоящее время я не могу заставить вашу технику работать 2. Очень расстраивает, что это так сложно загружать данные конфигурации из файла удаления в программе Angular при запуске
Бернард
@ Alkaline Я узнал одну или две вещи с этого поста. Ответ теоретически верен, но имеет 1 или 2 вещи (1, на которые вы указали), которые следует исправить. Спасибо за комментарий. Я рассмотрю и обновлю ответ. Удалил кодовую ручку на данный момент ... никогда не было возможности закончить его.
Брайан Вандербуш
5
Я думаю, что информация, которую вы предоставили здесь, неверна. Вы можете использовать провайдера, но на этапе настройки вы не работаете с результатом $getвызова. Вместо этого вы хотите добавить методы в экземпляр провайдера и просто вернуться thisпри вызове $get. Фактически, в вашем примере вы могли бы просто использовать сервис ... В провайдере вы также не можете вводить такие сервисы, как $http. И, кстати, это //return the factory as a provider, that is available during the configuration phaseвводит в заблуждение / неверная информация
Дитерг
21

Краткий ответ: вы не можете. AngularJS не позволит вам внедрять сервисы в конфигурацию, потому что не может быть уверен, что они были загружены правильно.

Посмотрите этот вопрос и ответ: AngularJS-инъекция значения внутри модуля.config

Модуль - это набор блоков конфигурации и запуска, которые применяются к приложению во время процесса начальной загрузки. В простейшем виде модуль состоит из двух блоков:

Блоки конфигурации - выполняются на этапе регистрации и настройки провайдера. Только провайдеры и константы могут быть введены в блоки конфигурации. Это необходимо для предотвращения случайного создания служб до их полной настройки.

Алекс Осборн
источник
2
на самом деле, это может быть сделано. Предоставление ответа с кратким объяснением.
Брайан Вандербуш
5

Я не думаю, что вы должны быть в состоянии сделать это, но я успешно внедрил службу в configблок. (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();
                    }
                });
            };
        }
    ]);
kim3er
источник
имя метода службы - dogmaCacheBuster, но .configвы написали cacheBuster (который нигде не определен в ответе) и dogmaCacheBusterProvider (который больше не используется). Вы проясните это?
DiEcho
@ pro.mean Я думаю, что демонстрировал технику, которую использовал, чтобы вставить сервис в блок конфигурации, но это было давно. cacheBusterопределяется как параметр функции config. Что касается dogmaCacheBusterProviderтого, что Angular делает с соглашениями об именах, что я давно забыл. Это может приблизить вас, stackoverflow.com/a/20881705/110010 .
kim3er
на это еще одна ссылка. Я узнаю, что поставщик дополнений, что мы определяем в .provider()рецепте. Что если я определю что-то с помощью .factory('ServiceName')или .service('ServiceName')рецептом и захочу использовать один из его методов в .config блоке, установите параметр как ServiceNameProvider, но это остановит мое приложение.
diEcho
5

Вы можете использовать сервис $ inject для внедрения сервиса в вашу конфигурацию

app.config (функция ($ предоставить) {

    $ provide.decorator ("$ exceptionHandler", функция ($ делегат, $ инжектор) {
        функция возврата (исключение, причина) {
            var $ rootScope = $ injector.get ("$ rootScope");
            $ rootScope.addError ({сообщение: «Исключение», причина: исключение});
            $ делегат (исключение, причина);
        };
    });

});

Источник: http://odetocode.com/blogs/scott/archive/2014/04/21/better-error-handling-in-angularjs.aspx

Корай Гюклю
источник
5

** Явно запрашивать услуги у других модулей, используя angular.injector **

Просто для уточнения ответа kim3er , вы можете предоставлять услуги, фабрики и т. Д., Не меняя их на провайдеров, если они включены в другие модули ...

Тем не менее, я не уверен, *Providerвсегда ли будет доступен (который выполняется внутренне с помощью angular после того, как он обрабатывает сервис или фабрику) (это может зависеть от того, что еще загружено первым), так как угловые лениво загружают модули.

Обратите внимание, что если вы хотите повторно ввести значения, они должны рассматриваться как константы.

Вот более явный и, вероятно, более надежный способ сделать это + рабочий поршень

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
источник
2

Использование $ инжектора для вызова методов службы в конфигурации

У меня была похожая проблема, и я решил ее с помощью службы $ injector, как показано выше. Я пытался внедрить сервис напрямую, но в итоге получал круговую зависимость от $ http. Сервис отображает модальное сообщение об ошибке, и я использую модальный интерфейс ui-bootstrap, который также зависит от $ 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.")
            }
        }
    }
Линкольн Спитери
источник
Спасибо, что очень помогли
Эрез
2

Решение очень легко сделать это

Примечание : это только для асинхронного вызова, потому что служба не инициализируется при выполнении конфигурации.

Вы можете использовать run()метод. Пример :

  1. Ваш сервис называется "MyService"
  2. Вы хотите использовать его для асинхронного выполнения на провайдере «MyProvider»

Ваш код :

(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
источник
1

Ну, я немного боролся с этим, но я действительно сделал это.

Я не знаю, устарели ли ответы из-за некоторого изменения в угловых значениях, но вы можете сделать это следующим образом:

Это ваш сервис:

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

И это конфиг

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

  resolve: {
    beer: function(beerRetrievalService) {
      return beerRetrievalService.getRandomBeer();
    }
  }
})
Луис Сиира
источник
0

Самый простой способ: $injector = angular.element(document.body).injector()

Затем используйте это для запуска invoke()илиget()

Pencilcheck
источник
Что за хак! К сожалению, он не будет работать с большинством модульных тестов, где приложение не привязано к DOM.
rixo