Служба впорскування в 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;
        }
    };
});

Налаштування:

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

Але я отримую цю помилку:

Помилка: невідомий постачальник: dbService від EditorApp

Як виправити налаштування та ввести цю послугу?


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

Відповіді:


131

Олексій вказав правильну причину того, що не в змозі робити те, що ти намагаєшся, тому +1. Але ви стикаєтеся з цією проблемою, оскільки ви не зовсім використовуєте вирішення способів їх проектування.

resolveприймає або рядок послуги, або функцію, що повертає значення, яке потрібно ввести. Оскільки ви робите останнє, вам потрібно перейти до фактичної функції:

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

Коли фреймворк вирішиться data, він введе функцію dbServiceв функцію, щоб ви могли вільно її використовувати. Не потрібно вводити його вconfig блок.

Смачного!


2
Дякую! Однак якщо я це роблю, я отримую: Помилка: 'undefined' не є об’єктом (оцінюючи '$ q.defer') у службі.
дендр

1
Ін'єкція відбувається у функції верхнього рівня, переданої на .service, тому рухайтесь $qі $httpтуди.
Джош Девід Міллер

1
@XMLilley Рядок у роздільній здатності - це фактично назва служби, а не конкретна функція служби. Використовуючи свій приклад, ви могли б зробити pageData: 'myData', але вам доведеться дзвонити pageData.overviewз вашого контролера. Метод рядків, ймовірно, корисний лише в тому випадку, якщо фабрика послуг повернула обіцянку замість API. Тож спосіб, який ви зараз робите це, мабуть, найкращий спосіб.
Джош Девід Міллер

2
@BrianVanderbusch Я мушу визнати деяку плутанину щодо того, де саме ти вважаєш, що ми не згодні. Справжньою проблемою, з якою стикався ОП, було те, що він ввів службу в блок налаштувань, чого неможливо зробити . Рішення - ввести послугу в роздільну здатність . Хоча у вашій відповіді було багато деталей щодо конфігурації сервісу, я не бачу, як це якось пов’язано з помилкою, з якою сталася ОП, і ваше рішення проблеми ОП було точно таким же : ви ввели службу у функцію вирішення та не функція конфігурації. Чи можете ви детальніше пояснити, де ми тут не згодні?
Джош Девід Міллер

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

Тепер у вас є налаштований користувальницький постачальник, вам просто потрібно ввести його. Ключовою відмінністю тут є відсутність "Постачальника у ваших ін'єкційних".

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

використовувати вирішені дані у вашому 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методу, що робить його кращим як постачальник рівнів додатків, але ви також можете інкапсулювати це всередині самого конфігураційного об'єкта, ввівши $provideв конфігурацію так:


2
Дякую, я шукав такий приклад; детальна відповідь та чудові посилання!
cnlevy

1
нема проблем! Це моя улюблена відповідь на ТАК. Я відповів на це, коли вже прийнято відповідь, і було 18 голосів. Добре для кількох значків!
Брайан Вандербуш

Було б дуже корисно, якби ваші зразки кодена працювали Наприклад, $ provide.service ('dbService', function () {не вводить $ http, але використовує його в своєму тілі. Як відомо, я не міг би змусити вашу техніку 2 працювати. Це дуже неприємно, що це так важко завантажити конфігураційні дані з видалення файлу в програмі Angular при запуску.
Бернар

@Alkaline Я дізнався щось або дві з цього посту. Відповідь теоретично правильна, але має 1 або 2 речі (1 ви вказали), які слід виправити. Дякуємо за коментар Я перегляну і оновлю відповідь. На даний момент видалено кодек ... ніколи не було можливості його закінчити.
Брайан Вандербуш

5
Я вважаю, що надана вами інформація тут невірна. Ви можете використовувати провайдера, але під час фази налаштування ви не працюєте з результатом $getдзвінка. Натомість ви хочете додати методи до екземпляра постачальника і просто повертатися, thisколи ви телефонуєте $get. Насправді у вашому прикладі ви могли просто скористатися послугою ... У постачальника ви також не можете вводити такі послуги, як $http. І btw ця //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();
                    }
                });
            };
        }
    ]);

Назва методу обслуговування - 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 ("$ изключенняHandler", функція ($ делегат, $ інжектор) {
        функція повернення (виняток, причина) {
            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(який зроблений внутрішньо кутовим після того, як він обробляє послугу чи завод), завжди буде доступний (це може залежати від того, що ще завантажено спочатку), як кутовий ліниво завантажує модулі.

Зауважте, що якщо ви хочете повторно ввести значення, їх слід розглядати як константи.

Ось більш чіткий і, мабуть, більш надійний спосіб зробити це + робочий плункер

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

Використання $ injector для виклику методів обслуговування в config

У мене була подібна проблема, і я вирішив її, скориставшись послугою $ 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;
    }]);
});

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


Який хак! На жаль, він не працюватиме з більшістю одиничних тестів, де програма не прив’язана до DOM.
rixo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.