188 lines
11 KiB
HTML
Executable file
188 lines
11 KiB
HTML
Executable file
<a href="http://github.com/angular/angular.js/edit/master/docs/content/tutorial/step_05.ngdoc" class="improve-docs btn btn-primary"><i class="icon-edit"> </i> Improve this doc</a><h1><code ng:non-bindable=""></code>
|
|
<div><span class="hint"></span>
|
|
</div>
|
|
</h1>
|
|
<div><div class="tutorial-page tutorial-5-xhrs-dependency-injection-page"><ul doc-tutorial-nav="5"></ul>
|
|
|
|
|
|
<p>Enough of building an app with three phones in a hard-coded dataset! Let's fetch a larger dataset
|
|
from our server using one of angular's built-in <a href="api/ng">services</a> called <a href="api/ng.$http"><code>$http</code></a>. We will use angular's <a href="guide/di">dependency injection (DI)</a> to provide the service to the <code>PhoneListCtrl</code> controller.</p>
|
|
<div doc-tutorial-reset="5">
|
|
</div>
|
|
|
|
|
|
<p>You should now see a list of 20 phones.</p>
|
|
<p>The most important changes are listed below. You can see the full diff on <a href="https://github.com/angular/angular-phonecat/compare/step-4...step-5">GitHub</a>:</p>
|
|
<h3>Data</h2>
|
|
<p>The <code>app/phones/phones.json</code> file in your project is a dataset that contains a larger list of phones
|
|
stored in the JSON format.</p>
|
|
<p>Following is a sample of the file:
|
|
<pre class="prettyprint linenums">
|
|
[
|
|
{
|
|
"age": 13,
|
|
"id": "motorola-defy-with-motoblur",
|
|
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
|
|
"snippet": "Are you ready for everything life throws your way?"
|
|
...
|
|
},
|
|
...
|
|
]
|
|
</pre>
|
|
<h2>Controller</h2>
|
|
<p>We'll use angular's <a href="api/ng.$http"><code>$http</code></a> service in our controller to make an HTTP
|
|
request to your web server to fetch the data in the <code>app/phones/phones.json</code> file. <code>$http</code> is just
|
|
one of several built-in <a href="guide/dev_guide.services">angular services</a> that handle common operations
|
|
in web apps. Angular injects these services for you where you need them.</p>
|
|
<p>Services are managed by angular's <a href="guide/di">DI subsystem</a>. Dependency injection
|
|
helps to make your web apps both well-structured (e.g., separate components for presentation, data,
|
|
and control) and loosely coupled (dependencies between components are not resolved by the
|
|
components themselves, but by the DI subsystem).</p>
|
|
<p><strong><code>app/js/controllers.js:</code></strong>
|
|
<pre class="prettyprint linenums">
|
|
function PhoneListCtrl($scope, $http) {
|
|
$http.get('phones/phones.json').success(function(data) {
|
|
$scope.phones = data;
|
|
});
|
|
|
|
$scope.orderProp = 'age';
|
|
}
|
|
|
|
//PhoneListCtrl.$inject = ['$scope', '$http'];
|
|
</pre>
|
|
<p><code>$http</code> makes an HTTP GET request to our web server, asking for <code>phone/phones.json</code> (the url is
|
|
relative to our <code>index.html</code> file). The server responds by providing the data in the json file.
|
|
(The response might just as well have been dynamically generated by a backend server. To the
|
|
browser and our app they both look the same. For the sake of simplicity we used a json file in this
|
|
tutorial.)</p>
|
|
<p>The <code>$http</code> service returns a <a href="api/ng.$q"><code>promise object</code></a> with a <code>success</code>
|
|
method. We call this method to handle the asynchronous response and assign the phone data to the
|
|
scope controlled by this controller, as a model called <code>phones</code>. Notice that angular detected the
|
|
json response and parsed it for us!</p>
|
|
<p>To use a service in angular, you simply declare the names of the dependencies you need as arguments
|
|
to the controller's constructor function, as follows:</p>
|
|
<pre><code>function PhoneListCtrl($scope, $http) {...}</code></pre>
|
|
<p>Angular's dependency injector provides services to your controller when the controller is being
|
|
constructed. The dependency injector also takes care of creating any transitive dependencies the
|
|
service may have (services often depend upon other services).</p>
|
|
<p>Note that the names of arguments are significant, because the injector uses these to look up the
|
|
dependencies.</p>
|
|
<p><img class="diagram" src="img/tutorial/xhr_service_final.png"></p>
|
|
<h4>'$' Prefix Naming Convention</h3>
|
|
<p>You can create your own services, and in fact we will do exactly that in step 11. As a naming
|
|
convention, angular's built-in services, Scope methods and a few other angular APIs have a '$'
|
|
prefix in front of the name. Don't use a '$' prefix when naming your services and models, in order
|
|
to avoid any possible naming collisions.</p>
|
|
<h3>A Note on Minification</h4>
|
|
<p>Since angular infers the controller's dependencies from the names of arguments to the controller's
|
|
constructor function, if you were to <a href="http://en.wikipedia.org/wiki/Minification_(programming)">minify</a> the JavaScript code for <code>PhoneListCtrl</code> controller, all of its function arguments would be
|
|
minified as well, and the dependency injector would not be able to identify services correctly.</p>
|
|
<p>To overcome issues caused by minification, just assign an array with service identifier strings
|
|
into the <code>$inject</code> property of the controller function, just like the last line in the snippet
|
|
(commented out) suggests:</p>
|
|
<pre><code>PhoneListCtrl.$inject = ['$scope', '$http'];</code></pre>
|
|
<p>There is also one more way to specify this dependency list and avoid minification issues — using the
|
|
bracket notation which wraps the function to be injected into an array of strings (representing the
|
|
dependency names) followed by the function to be injected:</p>
|
|
<pre><code>var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }];</code></pre>
|
|
<p>Both of these methods work with any function that can be injected by Angular, so it's up to your
|
|
project's style guide to decide which one you use.</p>
|
|
<h2>Test</h3>
|
|
<p><strong><code>test/unit/controllersSpec.js</code>:</strong></p>
|
|
<p>Because we started using dependency injection and our controller has dependencies, constructing the
|
|
controller in our tests is a bit more complicated. We could use the <code>new</code> operator and provide the
|
|
constructor with some kind of fake <code>$http</code> implementation. However, the recommended (and easier) way
|
|
is to create a controller in the test environment in the same way that angular does it in the
|
|
production code behind the scenes, as follows:</p>
|
|
<pre class="prettyprint linenums">
|
|
describe('PhoneCat controllers', function() {
|
|
|
|
describe('PhoneListCtrl', function(){
|
|
var scope, ctrl, $httpBackend;
|
|
|
|
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
|
|
// This allows us to inject a service but then attach it to a variable
|
|
// with the same name as the service.
|
|
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});
|
|
}));
|
|
</pre>
|
|
<p>Note: Because we loaded Jasmine and <code>angular-mocks.js</code> in our test environment, we got two helper
|
|
methods <a href="api/angular.mock.module"><code>module</code></a> and <a href="api/angular.mock.inject"><code>inject</code></a> that we'll
|
|
use to access and configure the injector.</p>
|
|
<p>We created the controller in the test environment, as follows:</p>
|
|
<ul>
|
|
<li><p>We used the <code>inject</code> helper method to inject instances of
|
|
<a href="api/ng.$rootScope"><code>$rootScope</code></a>,
|
|
<a href="api/ng.$controller"><code>$controller</code></a> and
|
|
<a href="api/ng.$httpBackend"><code>$httpBackend</code></a> services into the Jasmine's <code>beforeEach</code>
|
|
function. These instances come from an injector which is recreated from scratch for every single
|
|
test. This guarantees that each test starts from a well known starting point and each test is
|
|
isolated from the work done in other tests.</p>
|
|
</li>
|
|
<li><p>We created a new scope for our controller by calling <code>$rootScope.$new()</code></p>
|
|
</li>
|
|
<li><p>We called the injected <code>$controller</code> function passing the <code>PhoneListCtrl</code> function and the created
|
|
scope as parameters.</p>
|
|
</li>
|
|
</ul>
|
|
<p>Because our code now uses the <code>$http</code> service to fetch the phone list data in our controller, before
|
|
we create the <code>PhoneListCtrl</code> child scope, we need to tell the testing harness to expect an
|
|
incoming request from the controller. To do this we:</p>
|
|
<ul>
|
|
<li><p>Request <code>$httpBackend</code> service to be injected into our <code>beforeEach</code> function. This is a mock
|
|
version of the service that in a production environment facilitates all XHR and JSONP requests.
|
|
The mock version of this service allows you to write tests without having to deal with
|
|
native APIs and the global state associated with them — both of which make testing a nightmare.</p>
|
|
</li>
|
|
<li><p>Use the <code>$httpBackend.expectGET</code> method to train the <code>$httpBackend</code> service to expect an incoming
|
|
HTTP request and tell it what to respond with. Note that the responses are not returned until we call
|
|
the <code>$httpBackend.flush</code> method.</p>
|
|
</li>
|
|
</ul>
|
|
<p>Now, we will make assertions to verify that the <code>phones</code> model doesn't exist on <code>scope</code> before
|
|
the response is received:</p>
|
|
<pre class="prettyprint linenums">
|
|
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'}]);
|
|
});
|
|
</pre>
|
|
<ul>
|
|
<li><p>We flush the request queue in the browser by calling <code>$httpBackend.flush()</code>. This causes the
|
|
promise returned by the <code>$http</code> service to be resolved with the trained response.</p>
|
|
</li>
|
|
<li><p>We make the assertions, verifying that the phone model now exists on the scope.</p>
|
|
</li>
|
|
</ul>
|
|
<p>Finally, we verify that the default value of <code>orderProp</code> is set correctly:</p>
|
|
<pre class="prettyprint linenums">
|
|
it('should set the default value of orderProp model', function() {
|
|
expect(scope.orderProp).toBe('age');
|
|
});
|
|
</pre>
|
|
<p>You should now see the following output in the Karma tab:</p>
|
|
<pre><code> Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)</code></pre>
|
|
<h2>Experiments</h1>
|
|
<ul>
|
|
<li><p>At the bottom of <code>index.html</code>, add a <code>{{phones | json}}</code> binding to see the list of phones
|
|
displayed in json format.</p>
|
|
</li>
|
|
<li><p>In the <code>PhoneListCtrl</code> controller, pre-process the http response by limiting the number of phones
|
|
to the first 5 in the list. Use the following code in the $http callback:</p>
|
|
<pre><code> $scope.phones = data.splice(0, 5);</code></pre>
|
|
</li>
|
|
</ul>
|
|
<h1>Summary</h2>
|
|
<p>Now that you have learned how easy it is to use angular services (thanks to Angular's dependency
|
|
injection), go to <a href="tutorial/step_06">step 6</a>, where you will add some
|
|
thumbnail images of phones and some links.</p>
|
|
<ul doc-tutorial-nav="5"></ul></div></div>
|