Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

AngularJS XHR and Dependency Injection


May 08, 2021 AngularJS


Table of contents


XHR and dependency injection

This article focuses on AngularJS XHR and dependency injection, where details and sample code are organized

There are three phones in a hard-coded data set, and building an app is enough! Let's use one of Angel's built-in services, $http to get a larger data set from the server, and we'll use Angular's Dependency Injection (DI) to PhoneListCtrl controller.

  • There is now a list of 20 phones loaded from the server.

Reset the workspace to step five

git checkout -f step-5

Refresh your browser or check this step online: Step 5 Live Demo

The most important differences between step four and step five are listed below. You can see the full difference in GitHub.

Data

In your project, app/phones/phones.json is a dataset that contains a larger list of phones stored in JSON format.

Follow the following file example:

[
 {
  "age": 13,
  "id": "motorola-defy-with-motoblur",
  "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
  "snippet": "Are you ready for everything life throws your way?"
  ...
 },
...
]

Controller

We will use Angular's $http service in the controller to make HTTP requests to your Web server to get back the data from the app/phones/phones.json file. $http one of several built-in Angular services that handle common operations in Web applications. Angular injects these services where you need them.

Angular's DI subsysys system is responsible for managing these services. Dependency injection is useful for your web application to be both well-structured (for example, separating the performance layer, data, and control) and loose coupling (dependency issues between components that cannot be solved by the components themselves, solved by the DI subsyscies).

app/js/controllers.js:

var phonecatApp = angular.module('phonecatApp', []);

phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
  $http.get('phones/phones.json').success(function(data) {
    $scope.phones = data;
  });

  $scope.orderProp = 'age';
});

$http to send an HTTP GET request to your Web server that phones/phones.json (the url is a index.html T he server provides this data in the jason file in response to the request. ( The response may be dynamically generated by the back-end server.) B ut in the eyes of browsers and our apps, they're no different. F or simplicity, we used a johnson file in this tutorial.

The $http the service returns a promise object, with the success method. W e call this method to handle asynchronous responses and assume that the scope of the phone data is controlled by the controller, as a module called phones Note that Angular detected the johnson response and parsed it for us.

To use a service in Angular, you simply declare the name of the dependency you need as an argument to the controller's constructor, as follows:

phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...}

When you construct a controller, Angular's dependency injector injects these services into your controller. These dependency controllers are also responsible for creating any delivery dependencies that the service might require (one service typically depends on other services).

Note that the names of the parameters are important because the injector uses those names to look up dependencies.

AngularJS XHR and Dependency Injection

$ The prefix name convention

You can create your own service, and we'll actually do this in Step 11 AngularJS REST and Custom Services. As a naming convention, Angular's built-in services, scope methods, and some other Angular APIs use a $ prefix $ naming.

The namespace of the service provided by Angular has $ To avoid conflicts, it's a good idea to avoid naming your services and modules as prefixes. $

If you examine a scope, you may also notice that some properties start $$ These properties are considered private and cannot be accessed or modified.

A note on extreme simplification

Because Angel calls the controller's dependencies from the name of the argument to the controller constructor's function, if you plan to shrink the JavaScript code for PhoneListCtrl all function parameters are compressed, and the dependency injector will not correctly recognize the service.

We can overcome this problem by commenting this function with a dependency name, which is provided as a string and is not compressed. There are two ways to provide this injection comment:

  • Create a property in the $inject function that can carry an array of strings. E ach string in the array is the name of the service to be injected into the corresponding parameter. We can write this in our own example:

        function PhoneListCtrl($scope, $http) {...}
        PhoneListCtrl.$inject = ['$scope', '$http'];
        phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
  • Using an inline comment there is not just for this function, you also provide an array. This array contains a series of service names, followed by the function itself.

        function PhoneListCtrl($scope, $http) {...}
        phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);

Both methods work perfectly with any function that Angular injects, so which method to choose depends entirely on the programming style of your project.

If you use the second method, when registering a controller, you typically provide an inline constructor function in the form of an anonymous function.

    phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {...}]);

From this point on, we'll use the inline method in this tutorial. With that in in place, let's add the PhoneListCtrl

app/js/controllers.js:

var phonecatApp = angular.module('phonecatApp', []);

phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',
  function ($scope, $http) {
    $http.get('phones/phones.json').success(function(data) {
      $scope.phones = data;
    });

    $scope.orderProp = 'age';
  }]);

Test

test/unit/controllersSpec.js :

Since we started using dependency injection, and our controllers contain dependencies, constructing controllers in our tests became a bit complicated. W e can new operator and provide a constructor with some $http implemented by the computer. H owever, Angular provides an analog $http that we can use in unit tests. We configured a $httpBackend server request by calling a method called the server service.

describe('PhoneCat controllers', function() {

  describe('PhoneListCtrl', function(){
    var scope, ctrl, $httpBackend;

    // 在每次测试之前载入我们的应用模块定义
    beforeEach(module('phonecatApp'));

    // 注入器会忽略前面和后面的下划线(例如_$httpBackend_)。
    // 这允许我们注入一个服务,然后把它附加到同名变量上,以避免名称冲突
    beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
      $httpBackend = _$httpBackend_;
      $httpBackend.expectGET('phones/phones.json').
          respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);

      scope = $rootScope.$new();
      ctrl = $controller('PhoneListCtrl', {$scope: scope});
    }));

Note: Because we loaded Jasmine and angular-mocks.js we got two secondary methods, module and inject, to access and configure the injector.

We create controllers in a test environment, as follows:

  • We use inject assist method to inject instances of $rootScope, $controller, and $httpBackend services into Jasmine's beforeEach functions, which come from an injector that is recreated within each test. This ensures that each test starts from a well-known starting point, and that each test is independent of the others.
  • Create a $rootScope.$new() by calling the called .$new().
  • The injected $controller $controller to pass in PhoneListCtrl controller as an argument.

Because our code now $http get back the phone list data from our controller, we need to tell the test suite to wait for a later request from the controller before we create the PhoneListCtrl sub-scope. We can do this:

  • Request to $httpBackend service into our beforeEach function. T his is a simulated version of the service in the product environment that can respond to a variety of XHR and JSONP requests. The simulated version of the service allows you to write tests without having to deal with the native API and the global state associated with it -- both of which would have made testing a nightmare.

  • Use $httpBackend.expectGET $httpBackend request after the service waits and tells it how to respond to it. Note that the response is $httpBackend.flush the .flush method.

Now we make an assertion to verify that the phone module does not exist on the scope until the response arrives:

    it('should create "phones" model with 2 phones fetched from xhr', function() {
      expect(scope.phones).toBeUndefined();
      $httpBackend.flush();

      expect(scope.phones).toEqual([{name: 'Nexus S'},
                                   {name: 'Motorola DROID'}]);
    });
  • By calling $httpBackend.flush() we empty the request queue in the browser. T his causes $http returned by the service to be handled by the canon standard answer. You can find a full explanation of why httpBackend documentation must be "emptyed".

  • We made an assertion to verify that the phone module is already in scope.

Finally, we verify that the default value for orderProp set correctly.

    it('should set the default value of orderProp model', function() {
      expect(scope.orderProp).toBe('age');
    });

Now in the Karma tag card, you should see the following output:

Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)

Experiment

  • At .html bottom of index.html add a binding to view | | | a list of <pre>{{phones | filter:query | orderBy:orderProp | json}}</pre> displayed in json format.
  • In PhoneListCtrl the http response is preprocessed by limiting the number of phones to the first five of the list. Use $http in the callback:
$scope.phones = data.splice(0, 5);

Summarize

Now that you know how easy it is to use the Angular service (thanks to Angel's dependency injection), go to Step 6 template connections and images, where you'll add some thumbnails of your phone as well as some links.