Suntikkan layanan di app.config

168

Saya ingin menyuntikkan layanan ke app.config, sehingga data dapat diambil sebelum controller dipanggil. Saya mencobanya seperti ini:

Layanan:

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

Konfigurasi:

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

Tapi saya mendapatkan kesalahan ini:

Kesalahan: Penyedia tidak dikenal: dbService dari EditorApp

Bagaimana cara memperbaiki pengaturan dan menyuntikkan layanan ini?

dndr
sumber
3
meskipun apa yang telah Anda lihat sudah, ada adalah cara untuk mencapai apa yang Anda inginkan, dan AngularJS telah menghabiskan banyak waktu memungkinkan jenis fungsi. Tinjau jawaban saya tentang cara mencapai ini.
Brian Vanderbusch

Jawaban:

131

Alex memberikan alasan yang tepat untuk tidak dapat melakukan apa yang Anda coba lakukan, jadi +1. Tetapi Anda menghadapi masalah ini karena Anda tidak cukup menggunakan resolusi bagaimana mereka dirancang.

resolvemengambil string layanan atau fungsi mengembalikan nilai yang akan disuntikkan. Karena Anda melakukan yang terakhir, Anda harus memberikan fungsi yang sebenarnya:

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

Ketika kerangka kerja diselesaikan data, itu akan menyuntikkan dbServiceke dalam fungsi sehingga Anda dapat dengan bebas menggunakannya. Anda tidak perlu menyuntikkan ke configblok sama sekali untuk mencapai ini.

Selamat makan!

Josh David Miller
sumber
2
Terima kasih! Namun, jika saya melakukan ini saya mendapatkan: Kesalahan: 'tidak terdefinisi' bukan objek (mengevaluasi '$ q.defer') di layanan.
dndr
1
Injeksi terjadi pada fungsi level atas yang dilewati .service, jadi pindah $qdan ke $httpsana.
Josh David Miller
1
@XMLilley String dalam resolusi sebenarnya adalah nama layanan dan bukan fungsi tertentu pada layanan. Dengan menggunakan contoh Anda, Anda bisa melakukannya pageData: 'myData', tetapi kemudian Anda harus menelepon pageData.overviewdari pengontrol Anda. Metode string mungkin hanya berguna jika pabrik layanan mengembalikan janji alih-alih API. Jadi cara Anda saat ini melakukannya mungkin adalah cara terbaik.
Josh David Miller
2
@BrianVanderbusch Saya harus mengakui beberapa kebingungan di mana tepatnya Anda merasa kami tidak setuju. Sebenarnya masalah OP temui adalah bahwa ia menyuntikkan layanan ke blok config, yang tidak dapat dilakukan . Solusinya adalah menyuntikkan layanan ke tekad . Sementara jawaban Anda memberikan banyak detail tentang konfigurasi layanan, saya tidak melihat bagaimana hal itu terkait dengan kesalahan OP yang dihadapi, dan solusi Anda untuk masalah OP persis sama : Anda menyuntikkan layanan dalam fungsi penyelesaian dan bukan fungsi konfigurasi. Bisakah Anda menguraikan di mana kami tidak setuju di sini?
Josh David Miller
1
@JoshDavidMiller Menggunakan metode yang saya tunjukkan, adalah mungkin untuk mengkonfigurasi layanan sebelum aktivasi negara, sehingga kesalahan dapat dilemparkan / ditangani selama fase konfigurasi, berpotensi mengubah cara Anda akan instantiate nilai konfigurasi lainnya sebelum bootstrap aplikasi. Misalnya, menentukan peran pengguna agar aplikasi menyusun fitur yang tepat.
Brian Vanderbusch
140

Siapkan layanan Anda sebagai Penyedia AngularJS khusus

Terlepas dari apa yang dikatakan jawaban yang Diterima, Anda sebenarnya BISA melakukan apa yang ingin Anda lakukan, tetapi Anda perlu mengaturnya sebagai penyedia yang dapat dikonfigurasi, sehingga tersedia sebagai layanan selama fase konfigurasi .. Pertama, ubah Anda Serviceke penyedia seperti yang ditunjukkan di bawah ini. Perbedaan utama di sini adalah setelah menetapkan nilai defer, Anda menyetel defer.promiseproperti ke objek janji yang dikembalikan oleh $http.get:

Layanan Penyedia: (penyedia: resep layanan)

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

Sekarang, Anda memiliki Penyedia kustom yang dapat dikonfigurasi, Anda hanya perlu menyuntikkannya. Perbedaan utama di sini adalah "Penyedia injeksi Anda" yang hilang.

konfigurasi:

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

gunakan data terselesaikan di 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);
     }

})

Alternatif yang Mungkin

Alternatif berikut adalah pendekatan yang serupa, tetapi memungkinkan definisi terjadi di dalam .config, merangkum layanan ke dalam modul spesifik dalam konteks aplikasi Anda. Pilih metode yang tepat untuk Anda. Lihat juga di bawah untuk catatan tentang alternatif ke-3 dan tautan bermanfaat untuk membantu Anda memahami semua hal ini

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

Beberapa Sumberdaya yang bermanfaat

  • John Lindquist memiliki penjelasan dan demonstrasi 5 menit yang sangat baik di egghead.io , dan ini adalah salah satu pelajaran gratis! Saya pada dasarnya memodifikasi demonstrasi dengan membuatnya $httpspesifik dalam konteks permintaan ini
  • Lihat panduan Pengembang AngularJS di Penyedia
  • Ada juga penjelasan yang bagus tentang factory/ service/ provider di clevertech.biz .

Penyedia memberi Anda sedikit lebih banyak konfigurasi atas .servicemetode, yang membuatnya lebih baik sebagai penyedia tingkat aplikasi, tetapi Anda juga bisa merangkum ini dalam objek konfigurasi itu sendiri dengan menyuntikkan $provideke konfigurasi seperti:

Brian Vanderbusch
sumber
2
Terima kasih, saya mencari contoh seperti itu; jawaban terperinci dan tautan bagus!
cnlevy
1
tidak masalah! Ini jawaban favorit saya di SO. Saya menjawab ini ketika jawaban yang saat ini diterima sudah dijawab, dan memiliki 18 suara. Bagus untuk beberapa lencana!
Brian Vanderbusch
Ini akan sangat berguna jika sampel codepen Anda berfungsi. Sebagai contoh, $ supply.service ('dbService', function () {tidak memiliki $ http disuntikkan tetapi menggunakannya di dalam tubuhnya. Seperti berdiri, saya tidak bisa mendapatkan teknik 2 Anda untuk bekerja. Sangat frustasi karena itu adalah sangat sulit untuk memuat data konfigurasi dari file hapus di program Angular saat startup
Bernard

@Alkaline Saya telah belajar satu atau dua hal sejak posting ini. Jawabannya benar dalam teori tetapi memiliki 1 atau 2 hal (1 Anda tunjukkan) yang harus diperbaiki. Terima kasih atas komentarnya. Saya akan meninjau dan memperbarui jawabannya. Menghapus codepen untuk saat ini ... tidak pernah punya kesempatan untuk menyelesaikannya.
Brian Vanderbusch

5
Saya pikir informasi yang Anda berikan di sini salah. Anda dapat menggunakan penyedia tetapi selama fase konfigurasi Anda tidak bekerja dengan hasil $getpanggilan. Alih-alih, Anda ingin menambahkan metode pada instance penyedia dan kembali saja thisketika Anda menelepon $get. Bahkan dalam contoh Anda, Anda hanya bisa menggunakan layanan ... Di penyedia Anda juga tidak bisa menyuntikkan layanan seperti $http. Dan ini //return the factory as a provider, that is available during the configuration phaseadalah informasi yang menyesatkan / salah
Dieterg

21

Jawaban singkat: Anda tidak bisa. AngularJS tidak akan mengizinkan Anda untuk menyuntikkan layanan ke dalam konfigurasi karena tidak dapat memastikan mereka telah dimuat dengan benar.

Lihat pertanyaan dan jawaban ini: AngularJS ketergantungan injeksi nilai di dalam module.config

Modul adalah kumpulan konfigurasi dan menjalankan blok yang diterapkan ke aplikasi selama proses bootstrap. Dalam bentuknya yang paling sederhana, modul terdiri dari kumpulan dua jenis blok:

Blok konfigurasi - dijalankan selama registrasi penyedia dan fase konfigurasi. Hanya penyedia dan konstanta yang dapat disuntikkan ke blok konfigurasi. Ini untuk mencegah instantiasi layanan yang tidak disengaja sebelum mereka sepenuhnya dikonfigurasi.


2
sebenarnya, ini BISA dilakukan. Memberi jawaban dengan singkat.
Brian Vanderbusch

5

Saya tidak berpikir Anda seharusnya bisa melakukan ini, tetapi saya telah berhasil menyuntikkan layanan ke configblok. (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();
                    }
                });
            };
        }
    ]);

nama metode layanan adalah dogmaCacheBuster tetapi di dalamnya.config Anda telah menulis cacheBuster (yang tidak didefinisikan di mana pun dalam jawaban) dan dogmaCacheBusterProvider (yang tidak digunakan lebih lanjut). Apakah Anda akan menjelaskan hal ini?
diEcho

@ pro.mean Saya pikir saya mendemonstrasikan teknik yang saya gunakan, untuk memasukkan layanan ke dalam blok konfigurasi, tetapi sudah cukup lama. cacheBusterdidefinisikan, sebagai parameter dari fungsi konfigurasi. Sehubungan dengan dogmaCacheBusterProvideritu, sesuatu yang pintar yang dilakukan Angular dengan konvensi penamaan, yang sudah lama saya lupakan. Ini mungkin membuat Anda lebih dekat, stackoverflow.com/a/20881705/110010 .
kim3er

ini referensi lain. Saya jadi tahu bahwa menambahkan Penyedia apa pun yang kita definisikan dalam .provider()resep. bagaimana jika saya mendefinisikan sesuatu dengan .factory('ServiceName')atau .service('ServiceName')resep dan ingin menggunakan salah satu metodenya dalam .config blok, tetapkan parameternya sebagai ServiceNameProvider tetapi itu menghentikan aplikasi saya.
diEcho

5

Anda dapat menggunakan layanan $ inject untuk menyuntikkan layanan di konfigurasi Anda

app.config (fungsi ($ berikan) {

    $ supply.decorator ("$ exceptionHandler", function ($ delegate, $ injector) {
        mengembalikan fungsi (pengecualian, sebab) {
            var $ rootScope = $ injector.get ("$ rootScope");
            $ rootScope.addError ({message: "Exception", reason: exception});
            $ delegate (pengecualian, sebab);
        };
    });

});

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

Koray Güclü
Terima kasih telah membantu banyak
Erez
Apa yang hack! Sayangnya, itu tidak akan berfungsi dengan sebagian besar unit test di mana aplikasi tidak terikat ke DOM.
rixo
5

** Secara eksplisit meminta layanan dari modul lain menggunakan angular.injector **

Hanya untuk menguraikan jawaban kim3er , Anda dapat memberikan layanan, pabrik, dll tanpa mengubahnya ke penyedia, selama mereka termasuk dalam modul lain ...

Namun, saya tidak yakin apakah *Provider(yang dibuat secara internal oleh sudut setelah itu memproses layanan, atau pabrik) akan selalu tersedia (mungkin tergantung pada apa lagi yang dimuat pertama), karena sudut malas memuat modul.

Perhatikan bahwa jika Anda ingin menyuntikkan kembali nilai-nilai yang harus diperlakukan sebagai konstanta.

Berikut ini cara yang lebih eksplisit, dan mungkin lebih andal untuk melakukannya + seorang plunker yang berfungsi

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

Menggunakan $ injector untuk memanggil metode layanan di config

Saya memiliki masalah serupa dan mengatasinya dengan menggunakan layanan $ injector seperti yang ditunjukkan di atas. Saya mencoba menyuntikkan layanan secara langsung tetapi berakhir dengan ketergantungan melingkar pada $ http. Layanan menampilkan modal dengan kesalahan dan saya menggunakan modal ui-bootstrap yang juga memiliki ketergantungan pada $ 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
sumber
Terima kasih telah membantu banyak
Erez
2

Sebuah solusi yang sangat mudah dilakukan

Catatan : ini hanya untuk panggilan asynchrone, karena layanan tidak diinisialisasi pada eksekusi konfigurasi.

Anda bisa menggunakan run()metode. Contoh:

  1. Layanan Anda disebut "MyService"
  2. Anda ingin menggunakannya untuk eksekusi asynchrone pada penyedia "MyProvider"

Kode Anda:

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

Yah, saya sedikit berjuang dengan yang satu ini, tetapi saya benar-benar melakukannya.

Saya tidak tahu apakah jawabannya sudah usang karena beberapa perubahan sudut, tetapi Anda dapat melakukannya dengan cara ini:

Ini adalah layanan Anda:

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

Dan ini konfigurasi

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

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

Cara termudah: $injector = angular.element(document.body).injector()

Kemudian gunakan itu untuk menjalankan invoke()atauget()

Periksa pensil
sumber
Apa yang hack! Sayangnya, itu tidak akan berfungsi dengan sebagian besar unit test di mana aplikasi tidak terikat ke DOM.
rixo