diff options
| author | Igor Minar | 2012-03-30 14:02:26 -0700 |
|---|---|---|
| committer | Igor Minar | 2012-04-04 15:59:18 -0700 |
| commit | 6336b6e89e3a80aec3c4367ab4c2737fd365c030 (patch) | |
| tree | 31aa86a0555b541d1f6cc107845278ae80ddbff9 /docs/content/tutorial/step_03.ngdoc | |
| parent | fdf17d729fa7651e88dc5f27c40b8de875a34a55 (diff) | |
| download | angular.js-6336b6e89e3a80aec3c4367ab4c2737fd365c030.tar.bz2 | |
chore(docs): restore old tutorial ngdoc files
Diffstat (limited to 'docs/content/tutorial/step_03.ngdoc')
| -rw-r--r-- | docs/content/tutorial/step_03.ngdoc | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/docs/content/tutorial/step_03.ngdoc b/docs/content/tutorial/step_03.ngdoc new file mode 100644 index 00000000..fef4743f --- /dev/null +++ b/docs/content/tutorial/step_03.ngdoc @@ -0,0 +1,181 @@ +@ngdoc overview +@name Tutorial: 3 - Filtering Repeaters +@description + +<ul doc:tutorial-nav="3"></ul> + + +We did a lot of work in laying a foundation for the app in the last step, so now we'll do something +simple; we will add full text search (yes, it will be simple!). We will also write an end-to-end +test, because a good end-to-end test is a good friend. It stays with your app, keeps an eye on it, +and quickly detects regressions. + + +<doc:tutorial-instructions step="3"></doc:tutorial-instructions> + + +The app now has a search box. Notice that the phone list on the page changes depending on what a +user types into the search box. + +The most important differences between Steps 2 and 3 are listed below. You can see the full diff on +{@link https://github.com/angular/angular-phonecat/compare/step-2...step-3 + GitHub}: + + +## Controller + +We made no changes to the controller. + + +## Template + +__`app/index.html`:__ +<pre> +... + Fulltext Search: <input ng:model="query"/> + + <ul class="phones"> + <li ng:repeat="phone in phones.$filter(query)"> + {{phone.name}} + <p>{{phone.snippet}}</p> + </li> + </ul> +... +</pre> + +We added a standard HTML `<input>` tag and used angular's {@link api/angular.module.ng.$filter.filter $filter} +function to process the input for the `ng:repeater`. + +This lets a user enter search criteria and immediately see the effects of their search on the phone +list. This new code demonstrates the following: + +* Data-binding. This is one of the core features in Angular. When the page loads, Angular binds the +name of the input box to a variable of the same name in the data model and keeps the two in sync. + + In this code, the data that a user types into the input box (named __`query`__) is immediately +available as a filter input in the list repeater (`phone in phones.$filter(`__`query`__`)`). When +changes to the data model cause the repeater's input to change, the repeater efficiently updates +the DOM to reflect the current state of the model. + + <img src="img/tutorial/tutorial_03_final.png"> + +* Use of `$filter`. The {@link api/angular.module.ng.$filter.filter $filter} method uses the `query` value to +create a new array that contains only those records that match the `query`. + + `ng:repeat` automatically updates the view in response to the changing number of phones returned +by the `$filter`. The process is completely transparent to the developer. + +## Test + +In Step 2, we learned how to write and run unit tests. Unit tests are perfect for testing +controllers and other components of our application written in JavaScript, but they can't easily +test DOM manipulation or the wiring of our application. For these, an end-to-end test is a much +better choice. + +The search feature was fully implemented via templates and data-binding, so we'll write our first +end-to-end test, to verify that the feature works. + +__`test/e2e/scenarios.js`:__ +<pre> +describe('PhoneCat App', function() { + + describe('Phone list view', function() { + + beforeEach(function() { + browser().navigateTo('../../app/index.html'); + }); + + it('should filter the phone list as user types into the search box', function() { + expect(repeater('.phones li').count()).toBe(3); + + input('query').enter('nexus'); + expect(repeater('.phones li').count()).toBe(1); + + input('query').enter('motorola'); + expect(repeater('.phones li').count()).toBe(2); + }); + }); +}); +</pre> + +Even though the syntax of this test looks very much like our controller unit test written with +Jasmine, the end-to-end test uses APIs of {@link guide/dev_guide.e2e-testing Angular's end-to-end +test runner}. + +To run the end-to-end test, open one of the following in a new browser tab: + +* node.js users: {@link http://localhost:8000/test/e2e/runner.html} +* users with other http servers: +`http://localhost:[port-number]/[context-path]/test/e2e/runner.html` +* casual reader: {@link http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html} + +This test verifies that the search box and the repeater are correctly wired together. Notice how +easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it +really is that easy to set up any functional, readable, end-to-end test. + +# Experiments + +* Display the current value of the `query` model by adding a `{{query}}` binding into the +`index.html` template, and see how it changes when you type in the input box. + +* Let's see how we can get the current value of the `query` model to appear in the HTML page title. + + You might think you could just add the {{query}} to the title tag element as follows: + + <title>Google Phone Gallery: {{query}}</title> + + However, when you reload the page, you won't see the expected result. This is because the "query" +model lives in the scope defined by the body element: + + <body ng:controller="PhoneListCtrl"> + + If you want to bind to the query model from the `<title>` element, you must __move__ the +`ng:controller` declaration to the HTML element because it is the common parent of both the body +and title elements: + + <html ng:controller="PhoneListCtrl"> + + Be sure to *remove* the `ng:controller` declaration from the body element. + + While using double curlies works fine in within the title element, you might have noticed that +for a split second they are actually displayed to the user while the page is loading. A better +solution would be to use the {@link api/angular.directive.ng:bind ng:bind} or {@link +api/angular.directive.ng:bind-template ng:bind-template} directives, which are invisible to the +user while the page is loading: + + <title ng:bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title> + +* Add the following end-to-end test into the `describe` block within `test/e2e/scenarios.js`: + + <pre> + it('should display the current filter value within an element with id "status"', + function() { + expect(element('#status').text()).toMatch(/Current filter: \s*$/); + + input('query').enter('nexus'); + + expect(element('#status').text()).toMatch(/Current filter: nexus\s*$/); + + //alternative version of the last assertion that tests just the value of the binding + using('#status').expect(binding('query')).toBe('nexus'); + }); + </pre> + + Refresh the browser tab with the end-to-end test runner to see the test fail. To make the test +pass, edit the `index.html` template to add a `div` or `p` element with `id` `"status"` and content +with the `query` binding. + +* Add a `pause()` statement into an end-to-end test and rerun it. You'll see the runner pause; this +gives you the opportunity to explore the state of your application while it is displayed in the +browser. The app is live! You can change the search query to prove it. Notice how useful this is +for troubleshooting end-to-end tests. + + +# Summary + +We have now added full text search and included a test to verify that search works! Now let's go on +to {@link step_04 step 4} to learn how to add sorting capability to the phone app. + + +<ul doc:tutorial-nav="3"></ul> + |
