Tiêm dịch vụ trong app.config


168

Tôi muốn đưa một dịch vụ vào app.config để dữ liệu có thể được truy xuất trước khi bộ điều khiển được gọi. Tôi đã thử nó như thế này:

Dịch vụ:

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

Cấu hình:

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

Nhưng tôi nhận được lỗi này:

Lỗi: Nhà cung cấp không xác định: dbService từ EditorApp

Làm thế nào để sửa thiết lập và tiêm dịch vụ này?


3
mặc dù những gì bạn đã thấy rồi, có một cách để đạt được những gì bạn dự định, và AngularJS đã dành rất nhiều thời gian cho phép loại hình này các chức năng. Xem lại câu trả lời của tôi về cách đạt được điều này.
Brian Vanderbusch

Câu trả lời:


131

Alex cung cấp lý do chính xác cho việc không thể làm những gì bạn đang cố gắng thực hiện, vì vậy +1. Nhưng bạn đang gặp phải vấn đề này bởi vì bạn không hoàn toàn sử dụng giải quyết cách chúng được thiết kế.

resolvelấy chuỗi của dịch vụ hoặc hàm trả về giá trị được chèn. Vì bạn đang làm sau, bạn cần chuyển vào một chức năng thực tế:

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

Khi khung giải quyết data, nó sẽ đưa dbServicevào hàm để bạn có thể tự do sử dụng nó. Bạn không cần phải tiêm vào configkhối để hoàn thành việc này.

Chúc ngon miệng!


2
Cảm ơn! Tuy nhiên, nếu tôi làm điều này, tôi nhận được: Lỗi: 'không xác định' không phải là một đối tượng (đánh giá '$ q.defer') trong dịch vụ.
dndr

1
Việc tiêm xảy ra trong chức năng cấp cao nhất được chuyển đến .service, do đó di chuyển $q$httpở đó.
Josh David Miller

1
@XMLilley Chuỗi trong một giải quyết thực sự là tên của một dịch vụ và không phải là một chức năng cụ thể trên dịch vụ. Sử dụng ví dụ của bạn, bạn có thể làm pageData: 'myData', nhưng sau đó bạn sẽ phải gọi pageData.overviewtừ bộ điều khiển của bạn. Phương thức chuỗi có lẽ chỉ hữu ích nếu nhà máy dịch vụ trả lại lời hứa thay vì API. Vì vậy, cách bạn hiện đang làm có lẽ là cách tốt nhất.
Josh David Miller

2
@BrianVanderbusch Tôi phải thừa nhận một số nhầm lẫn về chính xác nơi bạn cảm thấy chúng tôi không đồng ý. Vấn đề thực tế mà OP gặp phải là anh ta đã chèn một dịch vụ vào một khối cấu hình, điều này không thể thực hiện được . Giải pháp là tiêm dịch vụ vào giải quyết . Mặc dù câu trả lời của bạn cung cấp rất nhiều chi tiết về cấu hình dịch vụ, tôi không thấy nó liên quan đến lỗi OP như thế nào và giải pháp của bạn cho vấn đề của OP hoàn toàn giống nhau : bạn đã tiêm dịch vụ vào chức năng giải quyết và không phải chức năng cấu hình. Bạn có thể giải thích về nơi chúng tôi không đồng ý ở đây?
Josh David Miller

1
@JoshDavidMiller Sử dụng phương pháp tôi đã trình bày, có thể định cấu hình dịch vụ trước khi kích hoạt trạng thái, để có thể ném / xử lý lỗi trong giai đoạn cấu hình, có khả năng thay đổi cách bạn khởi tạo các giá trị cấu hình khác trước khi khởi động ứng dụng. Ví dụ: xác định vai trò của người dùng để ứng dụng biên dịch các tính năng phù hợp.
Brian Vanderbusch

140

Thiết lập dịch vụ của bạn như một Nhà cung cấp AngularJS tùy chỉnh

Mặc dù câu trả lời được chấp nhận nói, bạn thực sự CÓ THỂ làm những gì bạn định làm, nhưng bạn cần thiết lập nó như một nhà cung cấp có thể định cấu hình, để nó có sẵn như một dịch vụ trong giai đoạn cấu hình .. Trước tiên, hãy đổi bạn Servicethành nhà cung cấp như hình dưới đây Sự khác biệt chính ở đây là sau khi đặt giá trị của defer, bạn đặt thuộc defer.promisetính thành đối tượng lời hứa được trả về bởi $http.get:

Nhà cung cấp dịch vụ: (nhà cung cấp: công thức dịch vụ)

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

Bây giờ, bạn có một Nhà cung cấp tùy chỉnh cấu hình, bạn chỉ cần tiêm nó. Sự khác biệt chính ở đây là "Nhà cung cấp trên tiêm" của bạn bị thiếu.

cấu hình:

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

sử dụng dữ liệu được giải quyết trong 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);
     }

})

Các lựa chọn thay thế có thể

Cách thay thế sau đây là một cách tiếp cận tương tự, nhưng cho phép định nghĩa xảy ra trong .config, đóng gói dịch vụ vào trong mô-đun cụ thể trong ngữ cảnh của ứng dụng của bạn. Chọn phương pháp phù hợp với bạn. Ngoài ra hãy xem bên dưới để biết các ghi chú về một liên kết thay thế và hữu ích thứ 3 để giúp bạn hiểu rõ tất cả những điều này

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

Một vài tài nguyên hữu ích

  • John Lindquist có một lời giải thích và trình diễn xuất sắc trong 5 phút về điều này tại egghead.io , và đó là một trong những bài học miễn phí! Về cơ bản tôi đã sửa đổi phần trình diễn của anh ấy bằng cách làm cho nó $httpcụ thể trong bối cảnh yêu cầu này
  • Xem hướng dẫn dành cho nhà phát triển AngularJS trên Nhà cung cấp
  • Ngoài ra còn có một lời giải thích tuyệt vời về factory/ service/ provider tại clevertech.biz .

Nhà cung cấp cung cấp cho bạn cấu hình nhiều hơn một chút so với .servicephương thức, điều này làm cho nó tốt hơn với tư cách là nhà cung cấp mức ứng dụng, nhưng bạn cũng có thể gói gọn điều này trong chính đối tượng cấu hình bằng cách đưa $providevào cấu hình như vậy:


2
Cảm ơn bạn, tôi đã tìm kiếm một ví dụ như vậy; câu trả lời chi tiết và các liên kết tuyệt vời!
cnlevy

1
không vấn đề gì! Đây là câu trả lời yêu thích của tôi về SO. Tôi đã trả lời điều này khi câu trả lời hiện được chấp nhận đã được trả lời và có 18 phiếu. Tốt cho một vài huy hiệu!
Brian Vanderbusch

Nó sẽ thực sự hữu ích nếu các mẫu codepen của bạn hoạt động. Chẳng hạn, $ cung cấp dịch vụ ('dbService', function () {không có $ http được tiêm nhưng sử dụng nó trong cơ thể của nó. quá khó để tải dữ liệu cấu hình từ một tệp xóa trong chương trình Angular khi khởi động.
Bernard

@Alkaline Tôi đã học được một hoặc hai điều từ bài đăng này. Câu trả lời là đúng trong lý thuyết nhưng có 1 hoặc 2 điều (1 bạn đã chỉ ra) cần được sửa. Cảm ơn các bình luận. Tôi sẽ xem xét và cập nhật câu trả lời. Đã xóa codepen vào lúc này ... không bao giờ có cơ hội để hoàn thành nó.
Brian Vanderbusch

5
Tôi nghĩ rằng thông tin bạn cung cấp ở đây là không chính xác. Bạn có thể sử dụng nhà cung cấp nhưng trong giai đoạn cấu hình, bạn không làm việc với kết quả của $getcuộc gọi. Thay vào đó, bạn muốn thêm các phương thức vào thể hiện của nhà cung cấp và chỉ quay lại thiskhi bạn gọi $get. Thực tế trong ví dụ của bạn, bạn chỉ có thể sử dụng một dịch vụ ... Trong một nhà cung cấp, bạn cũng không thể tiêm các dịch vụ như thế nào $http. Và btw đây //return the factory as a provider, that is available during the configuration phaselà thông tin sai lệch / không chính xác
Dieterg 17/03/2015

21

Câu trả lời ngắn gọn: bạn không thể. AngularJS sẽ không cho phép bạn tiêm dịch vụ vào cấu hình vì không thể chắc chắn rằng chúng đã được tải đúng cách.

Xem câu hỏi và câu trả lời này: Phép tiêm phụ thuộc AngularJS vào giá trị bên trong module.config

Một mô-đun là một tập hợp các cấu hình và chạy các khối được áp dụng cho ứng dụng trong quá trình bootstrap. Ở dạng đơn giản nhất, mô-đun bao gồm tập hợp hai loại khối:

Khối cấu hình - được thực hiện trong giai đoạn đăng ký và cấu hình của nhà cung cấp. Chỉ các nhà cung cấp và hằng số có thể được đưa vào các khối cấu hình. Điều này là để ngăn chặn việc khởi tạo ngẫu nhiên các dịch vụ trước khi chúng được cấu hình đầy đủ.


2
Trên thực tế, điều này CÓ THỂ được thực hiện. Cung cấp một câu trả lời giải thích ngắn gọn.
Brian Vanderbusch

5

Tôi không nghĩ bạn có thể làm điều này, nhưng tôi đã tiêm thành công một dịch vụ vào một configkhối. (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();
                    }
                });
            };
        }
    ]);

Tên phương thức dịch vụ là dogmaCacheBuster nhưng trong .configbạn đã viết cacheBuster (không được xác định ở bất kỳ đâu trong câu trả lời) và dogmaCacheBusterProvider (không được sử dụng thêm). Bạn sẽ làm rõ hơn về điều này?
diEcho

@ pro.mean Tôi nghĩ rằng tôi đã trình diễn kỹ thuật tôi đã sử dụng, để chèn một dịch vụ vào khối cấu hình, nhưng đã được một lúc rồi. cacheBusterđược định nghĩa, như là một tham số của chức năng cấu hình. Liên quan đến dogmaCacheBusterProvider, đó là một điều thông minh mà Angular làm với các quy ước đặt tên, mà tôi đã quên từ lâu. Điều này có thể giúp bạn đến gần hơn, stackoverflow.com/a/20881705/110010 .
kim3er

trên tài liệu tham khảo này. Tôi biết rằng thêm Nhà cung cấp bất cứ điều gì chúng tôi xác định trong .provider()công thức. Điều gì xảy ra nếu tôi xác định một cái gì đó với .factory('ServiceName')hoặc .service('ServiceName')công thức và muốn sử dụng một trong các phương thức của nó trong .config khối, đặt tham số là ServiceNameProvider nhưng nó dừng ứng dụng của tôi.
diEcho

5

Bạn có thể sử dụng dịch vụ $ chích để tiêm dịch vụ trong cấu hình của bạn

app.config (hàm ($ cung cấp) {

    $ cung cấp.decorator ("$ ngoại lệ", hàm ($ ủy nhiệm, $ kim phun) {
        Hàm trả về (ngoại lệ, nguyên nhân) {
            var $ rootScope = $ kim phun.get ("$ rootScope");
            $ rootScope.addError ({message: "Ngoại lệ", lý do: ngoại lệ});
            $ đại biểu (ngoại lệ, nguyên nhân);
        };
    });

});

Nguồn: http://odetocode.com/bloss/scott/archive/2014/04/21/better-error-handling-in-angularjs.aspx


5

** Yêu cầu rõ ràng các dịch vụ từ các mô-đun khác bằng angular.injection **

Chỉ cần giải thích câu trả lời của kim3er , bạn có thể cung cấp dịch vụ, nhà máy, v.v. mà không cần thay đổi chúng thành nhà cung cấp, miễn là chúng được bao gồm trong các mô-đun khác ...

Tuy nhiên, tôi không chắc chắn nếu *Provider(được tạo bởi nội bộ góc cạnh sau khi nó xử lý một dịch vụ hoặc nhà máy) sẽ luôn có sẵn (nó có thể phụ thuộc vào những gì khác được tải trước), vì các mô-đun tải góc cạnh.

Lưu ý rằng nếu bạn muốn tiêm lại các giá trị mà chúng nên được coi là hằng số.

Đây là một cách rõ ràng hơn và có lẽ đáng tin cậy hơn để làm điều đó + một plunker hoạt động

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

Sử dụng $ kim phun để gọi các phương thức dịch vụ trong cấu hình

Tôi đã có một vấn đề tương tự và giải quyết nó bằng cách sử dụng dịch vụ $ kim phun như hình trên. Tôi đã thử tiêm dịch vụ trực tiếp nhưng kết thúc với sự phụ thuộc vòng tròn vào $ http. Dịch vụ hiển thị một phương thức có lỗi và tôi đang sử dụng phương thức ui-bootstrap cũng có phụ thuộc vào $ 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.")
            }
        }
    }

Cảm ơn đã giúp rất nhiều
Erez

2

Một giải pháp rất dễ thực hiện

Lưu ý : nó chỉ dành cho một cuộc gọi asynchrone, vì dịch vụ không được khởi tạo khi thực hiện cấu hình.

Bạn có thể sử dụng run()phương pháp. Thí dụ :

  1. Dịch vụ của bạn được gọi là "MyService"
  2. Bạn muốn sử dụng nó để thực hiện asynchrone trên nhà cung cấp "MyProvider"

Ma cua ban :

(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

Vâng, tôi đã đấu tranh một chút với cái này, nhưng tôi thực sự đã làm nó.

Tôi không biết câu trả lời đã lỗi thời vì một số thay đổi trong góc, nhưng bạn có thể làm theo cách này:

Đây là dịch vụ của bạn:

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

Và đây là cấu hình

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

  resolve: {
    beer: function(beerRetrievalService) {
      return beerRetrievalService.getRandomBeer();
    }
  }
})

0

Cách dễ nhất: $injector = angular.element(document.body).injector()

Sau đó sử dụng nó để chạy invoke()hoặcget()


Thật là một hack! Thật không may, nó sẽ không hoạt động với hầu hết các bài kiểm tra đơn vị trong đó ứng dụng không được gắn với DOM.
rixo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.