บริการฉีดใน 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

จะแก้ไขการตั้งค่าและฉีดบริการนี้ได้อย่างไร?


3
แม้จะมีสิ่งที่คุณเห็นมาแล้ว แต่ก็มีวิธีที่จะบรรลุสิ่งที่คุณตั้งใจไว้และ AngularJS ใช้เวลามากมายในการเปิดใช้งานฟังก์ชั่นประเภทนี้ ทบทวนคำตอบของฉันเกี่ยวกับวิธีการบรรลุเป้าหมายนี้
Brian Vanderbusch

คำตอบ:


131

อเล็กซ์ให้เหตุผลที่ถูกต้องว่าไม่สามารถทำสิ่งที่คุณพยายามทำได้ +1 แต่คุณกำลังประสบปัญหานี้เพราะคุณไม่ค่อยได้ใช้วิธีแก้ปัญหาที่ออกแบบมา

resolveรับสายของบริการหรือฟังก์ชั่นคืนค่าที่จะฉีด เมื่อคุณทำสิ่งหลังคุณต้องผ่านฟังก์ชันจริง:

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

เมื่อเฟรมเวิร์กไปแก้ไขdataมันจะแทรกdbServiceเข้าไปในฟังก์ชั่นเพื่อให้คุณสามารถใช้งานได้อย่างอิสระ คุณไม่จำเป็นต้องฉีดเข้าไปในconfigบล็อกเลยเพื่อที่จะทำสิ่งนี้ให้สำเร็จ

อร่อย!


2
ขอบคุณ! อย่างไรก็ตามหากฉันทำสิ่งนี้ฉันจะได้รับ: ข้อผิดพลาด: 'ไม่ได้กำหนด' ไม่ใช่วัตถุ (ประเมิน '$ q.defer') ในบริการ
dndr

1
การฉีดเกิดขึ้นในฟังก์ชั่นระดับบนที่ส่งผ่านไป.serviceดังนั้นย้าย$qและ$httpมี
Josh David Miller

1
@XMLilley สตริงในการแก้ไขเป็นจริงชื่อของบริการและไม่ใช่ฟังก์ชั่นเฉพาะในบริการ โดยใช้ตัวอย่างของคุณคุณสามารถทำได้pageData: 'myData'แต่คุณจะต้องโทรpageData.overviewจากคอนโทรลเลอร์ของคุณ วิธีสตริงอาจมีประโยชน์เฉพาะเมื่อโรงงานบริการส่งคืนสัญญาแทน API ดังนั้นวิธีที่คุณกำลังทำอยู่อาจเป็นวิธีที่ดีที่สุด
Josh David Miller

2
@BrianVanderbusch ฉันต้องยอมรับความสับสนบางอย่างที่คุณรู้สึกว่าเราไม่เห็นด้วย ที่เกิดขึ้นจริงปัญหา OP ที่พบก็คือว่าเขาฉีดบริการลงในบล็อกการกำหนดค่าที่ไม่สามารถทำได้ การแก้ปัญหาคือการฉีดบริการลงในการแก้ปัญหา ในขณะที่คำตอบของคุณให้รายละเอียดมากมายเกี่ยวกับการกำหนดค่าบริการฉันไม่เห็นว่าเกี่ยวข้องกับวิธีการที่ข้อผิดพลาดที่พบ OP และวิธีแก้ไขปัญหา OP ของคุณนั้นเหมือนกันทุกประการ: คุณฉีดบริการในฟังก์ชั่นแก้ไขปัญหาและไม่ใช่ฟังก์ชั่นการตั้งค่า คุณช่วยอธิบายที่เราไม่เห็นด้วยกับที่นี่ได้ไหม?
Josh David Miller

1
@JoshDavidMiller การใช้วิธีที่ฉันแสดงมันเป็นไปได้ที่จะกำหนดค่าบริการก่อนการเปิดใช้งานสถานะเช่นว่าข้อผิดพลาดสามารถถูกโยน / จัดการในระหว่างขั้นตอนการตั้งค่าอาจเปลี่ยนแปลงวิธีที่คุณจะสร้างอินสแตนซ์ค่าการกำหนดค่าอื่น ๆ ตัวอย่างเช่นการกำหนดบทบาทของผู้ใช้เพื่อให้แอปพลิเคชันรวบรวมคุณสมบัติที่เหมาะสม
Brian Vanderbusch

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ห่อหุ้มบริการภายในโมดูลที่ระบุในบริบทของแอปของคุณ เลือกวิธีที่เหมาะสมกับคุณ ดูด้านล่างเพื่อดูบันทึกย่อในลิงค์ที่เป็นทางเลือกที่ 3 และลิงค์ที่มีประโยชน์เพื่อช่วยให้คุณไม่พลาดทุกสิ่งเหล่านี้

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

ทรัพยากรที่มีประโยชน์

  • John Lindquist มีคำอธิบายที่ยอดเยี่ยม 5 นาทีและสาธิตสิ่งนี้ที่egghead.ioและเป็นหนึ่งในบทเรียนฟรี! ฉันปรับเปลี่ยนการสาธิตของเขาโดยการทำให้เป็นเรื่อง$httpเฉพาะในบริบทของคำขอนี้
  • ดูคู่มือนักพัฒนา AngularJS เกี่ยวกับผู้ให้บริการ
  • นอกจากนี้ยังมีคำอธิบายที่ดีเกี่ยวกับfactory/ service/ provider ที่ clevertech.biz

ผู้ให้บริการให้การกำหนดค่าอีกเล็กน้อยสำหรับ.serviceวิธีการซึ่งจะทำให้ดีขึ้นในฐานะผู้ให้บริการระดับแอปพลิเคชัน แต่คุณสามารถห่อหุ้มสิ่งนี้ภายในวัตถุการกำหนดค่าเองโดยการฉีด$provideเข้าสู่การกำหนดค่าดังนี้


2
ขอบคุณฉันกำลังมองหาตัวอย่างเช่น; คำตอบโดยละเอียดและลิงก์ที่ยอดเยี่ยม!
cnlevy

1
ไม่มีปัญหา! นี่คือคำตอบที่ฉันโปรดปรานใน SO ฉันตอบคำถามนี้เมื่อคำตอบที่ตอบรับแล้วตอบไปแล้วและมี 18 คะแนน ดีสำหรับป้ายบาง!
Brian Vanderbusch

มันจะมีประโยชน์จริง ๆ ถ้าตัวอย่าง codepen ของคุณใช้ได้ ตัวอย่างเช่น $ give.service ('dbService', function () {ไม่มีการฉีด $ http แต่ใช้มันในร่างกายของมันเนื่องจากมันยืนอยู่ฉันไม่สามารถให้เทคนิคของคุณ 2 ใช้งานได้มันน่าหงุดหงิดมากที่มันเป็น จึงยากที่จะโหลดข้อมูลการกำหนดค่าจากไฟล์ลบในโปรแกรมเชิงมุมที่เริ่มต้น.
เบอร์นาร์ด

@ อัลคาไลน์ฉันได้เรียนรู้สิ่งหนึ่งหรือสองตั้งแต่โพสต์นี้ คำตอบนั้นถูกต้องในทางทฤษฎี แต่มี 1 หรือ 2 สิ่ง (1 คุณชี้ให้เห็น) ที่ควรได้รับการแก้ไข ขอบคุณสำหรับความคิดเห็น ฉันจะตรวจสอบและอัปเดตคำตอบ ลบ codepen สักครู่ ... ไม่เคยมีโอกาสทำให้เสร็จ
Brian Vanderbusch

5
ฉันคิดว่าข้อมูลที่คุณให้ไว้ที่นี่ไม่ถูกต้อง คุณสามารถใช้ผู้ให้บริการ แต่ในระหว่างขั้นตอนการกำหนดค่าคุณไม่ได้ทำงานกับผลลัพธ์ของการ$getโทร แต่คุณต้องการที่จะเพิ่มวิธีการในตัวอย่างเช่นผู้ให้บริการและเพิ่งกลับมาเมื่อคุณเรียกthis $getในความเป็นจริงในตัวอย่างของคุณคุณก็สามารถใช้บริการ ... $httpในผู้ให้บริการที่คุณยังไม่สามารถฉีดบริการเช่น และ btw นี้//return the factory as a provider, that is available during the configuration phaseเป็นข้อมูลที่ทำให้เข้าใจผิด / ไม่ถูกต้อง
Dieterg

21

คำตอบสั้น ๆ : คุณทำไม่ได้ AngularJS จะไม่อนุญาตให้คุณฉีดบริการลงในการกำหนดค่าเพราะไม่สามารถแน่ใจได้ว่าพวกเขาได้รับการโหลดอย่างถูกต้อง

ดูคำถามและคำตอบนี้: AngularJS การพึ่งพาการฉีดค่าภายใน module.config

โมดูลคือชุดของการกำหนดค่าและเรียกใช้บล็อกที่ถูกนำไปใช้กับแอปพลิเคชันในระหว่างกระบวนการ bootstrap ในรูปแบบที่ง่ายที่สุดโมดูลประกอบด้วยคอลเลกชันของบล็อกสองชนิด:

บล็อกการกำหนดค่า - รับการดำเนินการระหว่างการลงทะเบียนผู้ให้บริการและเฟสการกำหนดค่า ผู้ให้บริการและค่าคงที่เท่านั้นที่สามารถถูกฉีดเข้าไปในบล็อกการกำหนดค่า นี่คือการป้องกันไม่ให้มีอินสแตนซ์ของบริการโดยไม่ตั้งใจก่อนที่จะกำหนดค่าอย่างสมบูรณ์


2
จริง ๆ แล้วสิ่งนี้สามารถทำได้ การให้คำตอบอธิบายในไม่ช้า
Brian Vanderbusch

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ถูกกำหนดไว้เป็นพารามิเตอร์ของฟังก์ชั่นการตั้งค่า ด้วยความนับถือdogmaCacheBusterProviderมันเป็นสิ่งที่ชาญฉลาดที่ Angular ทำกับการตั้งชื่อการประชุมซึ่งฉันลืมไปนานแล้ว นี้อาจทำให้คุณได้ใกล้ชิดstackoverflow.com/a/20881705/110010
kim3er

ในการอ้างอิงอื่นนี้ ฉันรู้ว่าการผนวกสิ่งที่ผู้ให้บริการกำหนดใน.provider()สูตรต่อท้าย ถ้าฉันกำหนดบางสิ่งบางอย่างด้วย.factory('ServiceName')หรือ.service('ServiceName')สูตรอาหารและต้องการใช้วิธีใดวิธีหนึ่งใน.config บล็อกให้ตั้งค่าพารามิเตอร์เป็นServiceNameProviderแต่จะหยุดแอปพลิเคชันของฉัน
diEcho

5

คุณสามารถใช้บริการ $ inject เพื่อฉีดบริการที่คุณกำหนดค่า

app.config (ฟังก์ชั่น ($ ให้) {

    $ offer.decorator ("$ exceptionHandler", ฟังก์ชั่น ($ มอบหมาย, $ injector) {
        ฟังก์ชันส่งคืน (ข้อยกเว้นสาเหตุ) {
            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(ซึ่งทำขึ้นภายในโดยแองกูลาร์หลังจากดำเนินการบริการหรือโรงงาน) จะสามารถใช้งานได้เสมอ (ขึ้นอยู่กับสิ่งที่โหลดก่อนอื่น) เนื่องจากแองกูลาร์โหลดโมดูลอย่างเกียจคร้าน

โปรดทราบว่าถ้าคุณต้องการฉีดค่าที่พวกเขาควรจะถือว่าเป็นค่าคงที่

นี่คือวิธีที่ชัดเจนมากขึ้นและอาจจะเชื่อถือได้มากขึ้นที่จะทำมัน + 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();
});

2

ใช้ $ injector เพื่อโทรหาวิธีการบริการในการกำหนดค่า

ฉันมีปัญหาที่คล้ายกันและแก้ไขได้โดยใช้บริการ $ 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.")
            }
        }
    }

ขอบคุณที่ช่วยได้มาก
Erez

2

ทางออกที่ง่ายมากที่จะทำ

หมายเหตุ : ใช้สำหรับการเรียกใช้ asynchrone เท่านั้นเนื่องจากบริการไม่ได้ถูกกำหนดค่าเริ่มต้นสำหรับการดำเนินการกำหนดค่า

คุณสามารถใช้run()วิธีการ ตัวอย่าง:

  1. บริการของคุณเรียกว่า "MyService"
  2. คุณต้องการที่จะใช้มันสำหรับการดำเนินการ asynchrone ในผู้ให้บริการ "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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.