* docs(toh-6/dart): refactoring of 'add, edit, delete heroes' Refactoring of "add, edit, delete heroes" section of toh-6 from one big bottom-up step into small independent feature slices, where the user achieves a "milesone" (i.e., can run the full app) after each feature section. The section rewrite is shorter and offers a better UX. Other simplifications: - Error handling is consistent: in the hero service we log to the console, everwhere else we just let errors bubble up. - Hero service methods renamed based on function (create, update) rather then lower-level implementation (post, put). - @Output properties have been eliminated (since they weren't explained). E2E tests now pass on both the TS and Dart sides. * docs(toh-6/ts): refactoring of 'add, edit, delete heroes' Refactoring of "add, edit, delete heroes" section of toh-6 from one big bottom-up step into small independent feature slices, where the user achieves a "milesone" (i.e., can run the full app) after each feature section. The section rewrite is shorter and offers a better UX. Other simplifications: - Error handling is consistent: in the hero service we log to the console, everwhere else we just let errors bubble up. - Hero service methods renamed based on function (create, update) rather then lower-level implementation (post, put). - @Output properties have been eliminated (since they weren't explained). E2E tests now pass on both the TS and Dart sides. Post-Dart-review updates included. * docs(toh-6): ward tweaks
		
			
				
	
	
		
			192 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			192 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| extends ../../../ts/_cache/tutorial/toh-pt6.jade
 | |
| 
 | |
| block includes
 | |
|   include ../_util-fns
 | |
|   - var _Http = 'BrowserClient';
 | |
|   - var _Angular_Http = 'Dart <code>BrowserClient</code>'
 | |
|   - var _httpUrl = 'https://pub.dartlang.org/packages/http'
 | |
|   - var _Angular_http_library = 'Dart <a href="' + _httpUrl + '"><b>http</b></a> package'
 | |
|   - var _HttpModule = 'BrowserClient'
 | |
|   - var _JSON_stringify = 'JSON.encode'
 | |
| 
 | |
| block start-server-and-watch
 | |
|   :marked
 | |
|     ### Keep the app compiling and running
 | |
| 
 | |
|     Open a terminal/console window.
 | |
|     Start the Dart compiler, watch for changes, and start our server by entering the command:
 | |
| 
 | |
|   code-example(language="bash").
 | |
|       pub serve
 | |
| 
 | |
| block http-library
 | |
|   :marked
 | |
|     We'll be using the !{_Angular_http_library}'s
 | |
|     `BrowserClient` class to communicate with a server.
 | |
| 
 | |
|     ### Pubspec updates
 | |
| 
 | |
|     Update package dependencies by adding the
 | |
|     `stream_transformers` and !{_Angular_http_library}s.
 | |
| 
 | |
|     We also need to add a `resolved_identifiers` entry, to inform the [angular2
 | |
|     transformer][ng2x] that we'll be using `BrowserClient`. (For an explanation of why
 | |
|     this extra configuration is needed, see the [HTTP client chapter][guide-http].) We'll
 | |
|     also need to use `Client` from http, so let's add that now as well.
 | |
| 
 | |
|     Update `pubspec.yaml` to look like this (additions are highlighted):
 | |
| 
 | |
|     [guide-http]: ../guide/server-communication.html#!#http-providers
 | |
|     [ng2x]: https://github.com/angular/angular/wiki/Angular-2-Dart-Transformer
 | |
| 
 | |
|   - var stylePattern = { pnk: /(http.*|stream.*|resolved_identifiers:|Browser.*|Client.*)/gm };
 | |
|   +makeExcerpt('pubspec.yaml', 'additions', null, stylePattern)
 | |
| 
 | |
| block http-providers
 | |
|   :marked
 | |
|     Before our app can use `#{_Http}`, we have to register it as a service provider.
 | |
| 
 | |
|     We should be able to access `!{_Http}` services from anywhere in the application.
 | |
|     So we register it in the `bootstrap` call where we
 | |
|     launch the application and its root `AppComponent`.
 | |
| 
 | |
|   +makeExcerpt('app/main.ts','v1')
 | |
| 
 | |
|   :marked
 | |
|     Notice that we supply `!{_HttpModule}` in a list, as the second parameter to
 | |
|     the `bootstrap` method.  This has the same effect as the `providers` list in
 | |
|     `@Component` annotation.
 | |
| 
 | |
| block backend
 | |
|   :marked
 | |
|     We want to replace `BrowserClient`, the service that talks to the remote server,
 | |
|     with the in-memory web API service.
 | |
|     Our in-memory web API service, shown below, is implemented using the
 | |
|     `http` library `MockClient` class.
 | |
|     All `http` client implementations share a common `Client` interface, so
 | |
|     we'll have our app use the `Client` type so that we can freely switch between
 | |
|     implementations.
 | |
| 
 | |
| block dont-be-distracted-by-backend-subst
 | |
|   //- No backend substitution but we do need to comment on the changes to Hero.
 | |
|   :marked
 | |
|     As is common for web API services, our mock in-memory service will be
 | |
|     encoding and decoding heroes in JSON format, so we enhance the `Hero`
 | |
|     class with these capabilities:
 | |
| 
 | |
|   +makeExample('lib/hero.dart')
 | |
| 
 | |
| block get-heroes-details
 | |
|   :marked
 | |
|     To get the list of heroes, we first make an asynchronous call to
 | |
|     `http.get()`. Then we use the `_extractData` helper method to decode the
 | |
|     response body.
 | |
| 
 | |
| block observables-section-intro
 | |
|   :marked
 | |
|     Recall that `HeroService.getHeroes()` awaits for an `http.get()`
 | |
|     response and yields a _Future_ `List<Hero>`, which is fine when we are only
 | |
|     interested in a single result.
 | |
| 
 | |
| block search-criteria-intro
 | |
|   :marked
 | |
|     A [StreamController][], as its name implies, is a controller for a [Stream][] that allows us to
 | |
|     manipulate the underlying stream by adding data to it, for example.
 | |
| 
 | |
|     In our sample, the underlying stream of strings (`_searchTerms.stream`) represents the hero
 | |
|     name search patterns entered by the user. Each call to `search` puts a new string into
 | |
|     the stream by calling `add` over the controller.
 | |
| 
 | |
|     [Stream]: https://api.dartlang.org/stable/dart-async/Stream-class.html
 | |
|     [StreamController]: https://api.dartlang.org/stable/dart-async/StreamController-class.html
 | |
| 
 | |
| block observable-transformers
 | |
|   :marked
 | |
|     Fortunately, there are stream transformers that will help us reduce the request flow.
 | |
|     We'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
 | |
| 
 | |
|     * `transform(new Debounce(... 300)))` waits until the flow of search terms pauses for 300
 | |
|     milliseconds before passing along the latest string. We'll never make requests more frequently
 | |
|     than 300ms.
 | |
| 
 | |
|     * `distinct()` ensures that we only send a request if a search term has changed.
 | |
|     There's no point in repeating a request for the same search term.
 | |
| 
 | |
|     * `transform(new FlatMapLatest(...))` applies a map-like transformer that (1) calls our search
 | |
|     service for each search term that makes it through the debounce and distinct gauntlet and (2)
 | |
|     returns only the most recent search service result, discarding any previous results.
 | |
| 
 | |
|     * `handleError()` handles errors. Our simple example prints the error to the console; a real
 | |
|     life application should do better.
 | |
| 
 | |
| block filetree
 | |
|   .filetree
 | |
|     .file angular2_tour_of_heroes
 | |
|     .children
 | |
|       .file lib
 | |
|       .children
 | |
|         .file app_component.css
 | |
|         .file app_component.dart
 | |
|         .file dashboard_component.css
 | |
|         .file dashboard_component.dart
 | |
|         .file dashboard_component.html
 | |
|         .file hero.dart
 | |
|         .file hero_detail_component.css
 | |
|         .file hero_detail_component.dart
 | |
|         .file hero_detail_component.html
 | |
|         .file hero_search_component.css (new)
 | |
|         .file hero_search_component.dart (new)
 | |
|         .file hero_search_component.html (new)
 | |
|         .file hero_search_service.dart (new)
 | |
|         .file hero_service.dart
 | |
|         .file heroes_component.css
 | |
|         .file heroes_component.dart
 | |
|         .file heroes_component.html
 | |
|         .file in_memory_data_service.dart (new)
 | |
|       .file web
 | |
|       .children
 | |
|         .file main.dart
 | |
|         .file index.html
 | |
|         .file styles.css
 | |
|       .file pubspec.yaml
 | |
| 
 | |
| block file-summary
 | |
|   +makeTabs(
 | |
|     `toh-6/dart/lib/dashboard_component.dart,
 | |
|      toh-6/dart/lib/dashboard_component.html,
 | |
|      toh-6/dart/lib/hero.dart,
 | |
|      toh-6/dart/lib/hero_detail_component.dart,
 | |
|      toh-6/dart/lib/hero_detail_component.html,
 | |
|      toh-6/dart/lib/hero_service.dart,
 | |
|      toh-6/dart/lib/heroes_component.css,
 | |
|      toh-6/dart/lib/heroes_component.dart,
 | |
|      toh-6/dart/lib/in_memory_data_service.dart`,
 | |
|     ',,,,,,,,',
 | |
|     `lib/dashboard_component.dart,
 | |
|      lib/dashboard_component.html,
 | |
|      lib/hero.dart,
 | |
|      lib/hero_detail_component.dart,
 | |
|      lib/hero_detail_component.html,
 | |
|      lib/hero_service.dart,
 | |
|      lib/heroes_component.css,
 | |
|      lib/heroes_component.dart,
 | |
|      lib/in_memory_data_service.dart`)
 | |
| 
 | |
|   +makeTabs(
 | |
|     `toh-6/dart/lib/hero_search_component.css,
 | |
|      toh-6/dart/lib/hero_search_component.dart,
 | |
|      toh-6/dart/lib/hero_search_component.html,
 | |
|      toh-6/dart/lib/hero_search_service.dart`,
 | |
|     null,
 | |
|     `lib/hero_search_component.css,
 | |
|      lib/hero_search_component.dart,
 | |
|      lib/hero_search_component.html,
 | |
|      lib/hero_search_service.dart`)
 | |
| 
 | |
|   +makeTabs(
 | |
|     `toh-6/dart/pubspec.yaml,
 | |
|      toh-6/dart/web/main.dart`,
 | |
|     null,
 | |
|     `pubspec.yaml,
 | |
|      web/main.dart`)
 |