From cac5c8977f5052e47a89e97b8934f492f05eb8f1 Mon Sep 17 00:00:00 2001 From: Kathy Walrath Date: Fri, 8 Apr 2016 15:10:37 -0700 Subject: [PATCH] docs(toh/dart): convert toh-2 to dart (#1065) --- .../app_component_snippets_pt2.dart | 69 ++++ .../toh-2/dart/lib/app_component.dart | 114 +++++++ public/docs/_examples/toh-2/dart/pubspec.yaml | 15 + .../docs/_examples/toh-2/dart/web/index.html | 14 + .../docs/_examples/toh-2/dart/web/main.dart | 9 + public/docs/dart/latest/tutorial/toh-pt2.jade | 307 +++++++++++++++++- 6 files changed, 520 insertions(+), 8 deletions(-) create mode 100644 public/docs/_examples/toh-2/dart-snippets/app_component_snippets_pt2.dart create mode 100644 public/docs/_examples/toh-2/dart/lib/app_component.dart create mode 100644 public/docs/_examples/toh-2/dart/pubspec.yaml create mode 100644 public/docs/_examples/toh-2/dart/web/index.html create mode 100644 public/docs/_examples/toh-2/dart/web/main.dart diff --git a/public/docs/_examples/toh-2/dart-snippets/app_component_snippets_pt2.dart b/public/docs/_examples/toh-2/dart-snippets/app_component_snippets_pt2.dart new file mode 100644 index 0000000000..2e3e2438fd --- /dev/null +++ b/public/docs/_examples/toh-2/dart-snippets/app_component_snippets_pt2.dart @@ -0,0 +1,69 @@ +// #docregion ng-for +
  • + {{hero.id}} {{hero.name}} +
  • +// #enddocregion ng-for + +// #docregion heroes-styled +

    My Heroes

    + +// #enddocregion heroes-styled + +// #docregion selectedHero-click +
  • + {{hero.id}} {{hero.name}} +
  • +// #enddocregion selectedHero-click + +// #docregion selectedHero-details +

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    +// #enddocregion selectedHero-details + +// #docregion ng-if +
    +

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    +
    +// #enddocregion ng-if + +// #docregion hero-array-1 +final List heroes = mockHeroes; +// #enddocregion hero-array-1 + +// #docregion heroes-template-1 +

    My Heroes

    + +// #enddocregion heroes-template-1 + +// #docregion heroes-ngfor-1 +
  • +// #enddocregion heroes-ngfor-1 + +// #docregion class-selected-1 +[class.selected]="hero == selectedHero" +// #enddocregion class-selected-1 + +// #docregion class-selected-2 +
  • + {{hero.id}} {{hero.name}} +
  • +// #enddocregion class-selected-2 diff --git a/public/docs/_examples/toh-2/dart/lib/app_component.dart b/public/docs/_examples/toh-2/dart/lib/app_component.dart new file mode 100644 index 0000000000..53b534489b --- /dev/null +++ b/public/docs/_examples/toh-2/dart/lib/app_component.dart @@ -0,0 +1,114 @@ +// #docregion pt2 +import 'package:angular2/core.dart'; + +class Hero { + final int id; + String name; + + Hero(this.id, this.name); +} + +@Component( + selector: 'my-app', + template: ''' +

    {{title}}

    +

    My Heroes

    + +
    +

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    +
    + ''', +// #docregion styles-1 + styles: const [ + ''' + .selected { + background-color: #CFD8DC !important; + color: white; + } + .heroes { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 10em; + } + .heroes li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0em; + height: 1.6em; + border-radius: 4px; + } + .heroes li.selected:hover { + color: white; + } + .heroes li:hover { + color: #607D8B; + background-color: #EEE; + left: .1em; + } + .heroes .text { + position: relative; + top: -3px; + } + .heroes .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0em 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0px 0px 4px; + } + ''' + ]) +// #enddocregion styles-1 +class AppComponent { + final String title = 'Tour of Heroes'; + final List heroes = mockHeroes; +// #docregion selected-hero-1 + Hero selectedHero; +// #enddocregion selected-hero-1 + +// #docregion on-select-1 + onSelect(Hero hero) { + selectedHero = hero; + } +// #enddocregion on-select-1 +} +// #enddocregion pt2 + +// #docregion hero-array +final List mockHeroes = [ + new Hero(11, "Mr. Nice"), + new Hero(12, "Narco"), + new Hero(13, "Bombasto"), + new Hero(14, "Celeritas"), + new Hero(15, "Magneta"), + new Hero(16, "RubberMan"), + new Hero(17, "Dynama"), + new Hero(18, "Dr IQ"), + new Hero(19, "Magma"), + new Hero(20, "Tornado") +]; +// #enddocregion hero-array + +// #enddocregion pt2 diff --git a/public/docs/_examples/toh-2/dart/pubspec.yaml b/public/docs/_examples/toh-2/dart/pubspec.yaml new file mode 100644 index 0000000000..ddfafde27c --- /dev/null +++ b/public/docs/_examples/toh-2/dart/pubspec.yaml @@ -0,0 +1,15 @@ +name: angular2_tour_of_heroes +version: 0.0.1 +environment: + sdk: '>=1.13.0 <2.0.0' +dependencies: + angular2: 2.0.0-beta.1 + browser: ^0.10.0 + dart_to_js_script_rewriter: ^0.1.0+4 +transformers: +- angular2: + platform_directives: + - package:angular2/common.dart#COMMON_DIRECTIVES + platform_pipes: + - package:angular2/common.dart#COMMON_PIPES + entry_points: web/main.dart diff --git a/public/docs/_examples/toh-2/dart/web/index.html b/public/docs/_examples/toh-2/dart/web/index.html new file mode 100644 index 0000000000..3c09e8af9d --- /dev/null +++ b/public/docs/_examples/toh-2/dart/web/index.html @@ -0,0 +1,14 @@ + + + + Angular 2 Tour of Heroes + + + + + + + + Loading... + + diff --git a/public/docs/_examples/toh-2/dart/web/main.dart b/public/docs/_examples/toh-2/dart/web/main.dart new file mode 100644 index 0000000000..685cba5fce --- /dev/null +++ b/public/docs/_examples/toh-2/dart/web/main.dart @@ -0,0 +1,9 @@ +// #docregion pt2 +import 'package:angular2/bootstrap.dart'; + +import 'package:angular2_tour_of_heroes/app_component.dart'; + +main() { + bootstrap(AppComponent); +} +// #enddocregion pt2 \ No newline at end of file diff --git a/public/docs/dart/latest/tutorial/toh-pt2.jade b/public/docs/dart/latest/tutorial/toh-pt2.jade index 76a5523647..cbb88017a7 100644 --- a/public/docs/dart/latest/tutorial/toh-pt2.jade +++ b/public/docs/dart/latest/tutorial/toh-pt2.jade @@ -1,14 +1,305 @@ include ../_util-fns :marked - We're working on the Dart version of this case study. - In the meantime, please see these resources: + # It Takes Many Heroes + Our story needs more heroes. + We’ll expand our Tour of Heroes app to display a list of heroes, + allow the user to select a hero, and display the hero’s details. - * [Master/Detail](/docs/ts/latest/tutorial/toh-pt2.html): - The TypeScript version of this chapter + Let’s take stock of what we’ll need to display a list of heroes. + First, we need a list of heroes. We want to display those heroes in the view’s template, + so we’ll need a way to do that. - * [Dart source code](https://github.com/angular/angular.io/tree/master/public/docs/_examples/toh-5/dart): - A preliminary Dart version of the Tour of Heroes app, - featuring the hero editor, a master/detail page, - multiple components, services, and routing +.callout.is-helpful + header Source code + :marked + The complete source code for the example app in this chapter is + [in GitHub](https://github.com/angular/angular.io/tree/master/public/docs/_examples/toh-2/dart). +.l-main-section +:marked + ## Where We Left Off + Before we continue with Part 2 of the Tour of Heroes, + let’s verify we have the following structure after [Part 1](./toh-pt1.html). + If not, we’ll need to go back to Part 1 and figure out what we missed. + +.filetree + .file angular2_tour_of_heroes + .children + .file lib + .children + .file app_component.dart + .file web + .children + .file index.html + .file main.dart + .file pubspec.yaml +:marked + ### Keep the app transpiling and running + We want to start the Dart compiler, have it watch for changes, and start our server. We'll do this by typing + +code-example(format="." language="bash"). + pub serve + +:marked + This will keep the application running while we continue to build the Tour of Heroes. + +.l-main-section +:marked + ## Displaying Our Heroes + ### Creating heroes + Let’s create a list of ten heroes at the bottom of `app_component.dart`. + ++makeExample('toh-2/dart/lib/app_component.dart', 'hero-array', 'app_component.dart (Hero list)')(format=".") + +:marked + The `mockHeroes` list is of type `Hero`, the class defined in part one, + to create a list of heroes. + We aspire to fetch this list of heroes from a web service, but let’s take small steps + first and display mock heroes. + + ### Exposing heroes + Let’s create a public property in `AppComponent` that exposes the heroes for binding. + ++makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'hero-array-1', 'app_component.dart (Hero list property)') + +.l-sub-section + :marked + We could have defined the heroes list here in this component class. + But we know that ultimately we’ll get the heroes from a data service. + Because we know where we are heading, it makes sense to separate the hero data + from the class implementation from the start. +:marked + ### Displaying heroes in a template + Our component has `heroes`. Let’s create an unordered list in our template to display them. + We’ll insert the following chunk of HTML below the title and above the hero details. + ++makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'heroes-template-1', 'app_component.dart (Heroes template)')(format=".") + +:marked + Now we have a template that we can fill with our heroes. + + ### Listing heroes with ngFor + + We want to bind the list of `heroes` in our component to our template, iterate over them, + and display them individually. + We’ll need some help from Angular to do this. Let’s do this step by step. + + First modify the `
  • ` tag by adding the built-in directive `*ngFor`. + ++makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'heroes-ngfor-1', 'app_component.dart (ngFor)') + +.alert.is-critical + :marked + The leading asterisk (`*`) in front of `ngFor` is a critical part of this syntax. + +.l-sub-section + :marked + The (`*`) prefix to `ngFor` indicates that the `
  • ` element and its children + constitute a master template. + + The `ngFor` directive iterates over the `heroes` list returned by the `AppComponent.heroes` property + and stamps out instances of this template. + + The quoted text assigned to `ngFor` means + “*take each hero in the `heroes` list, store it in the local `hero` variable, + and make it available to the corresponding template instance*”. + + The `#` prefix before "hero" identifies the `hero` as a local template variable. + We can reference this variable within the template to access a hero’s properties. + + Learn more about `ngFor` and local template variables in the + [Displaying Data](../guide/displaying-data.html#ngFor) and + [Template Syntax](../guide/template-syntax.html#ngFor) chapters. + +:marked + Now we insert some content between the `
  • ` tags + that uses the `hero` template variable to display the hero’s properties. + ++makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'ng-for', 'app_component.dart (ngFor template)')(format=".") + +:marked + When the browser refreshes, we see a list of heroes! + + ### Styling our heroes + Our list of heroes looks pretty bland. + We want to make it visually obvious to a user which hero we are hovering over and which hero is selected. + + Let’s add some styles to our component by setting the `styles` argument of the `@Component` annotation + to the following CSS classes: + ++makeExample('toh-2/dart/lib/app_component.dart', 'styles-1', 'app_component.dart (Styling)')(format=".") + +:marked + Notice that we again use the triple-quote notation for multi-line strings. + + When we assign styles to a component they are scoped to that specific component. + Our styles will only apply to our `AppComponent` and won't "leak" to the outer HTML. + + Our template for displaying the heroes should now look like this: + ++makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'heroes-styled', 'app_component.dart (Styled heroes)') + +:marked + That's a lot of styles! We can put them inline as shown here, or we can move them out to their own file which will make it easier to code our component. + We'll do this in a later chapter. For now let's keep rolling. + +.l-main-section + :marked + ## Selecting a Hero + We have a list of heroes and we have a single hero displayed in our app. + The list and the single hero are not connected in any way. + We want the user to select a hero from our list, and have the selected hero appear in the details view. + This UI pattern is widely known as "master-detail". + In our case, the master is the heroes list and the detail is the selected hero. + + Let’s connect the master to the detail through a `selectedHero` component property bound to a click event. + + ### Click event + We modify the `
  • ` by inserting an Angular event binding to its click event. + + +makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'selectedHero-click', 'app_component.dart (Capturing the click event)')(format=".") + + :marked + Focus on the event binding + code-example(format="." language="bash"). + (click)="onSelect(hero)" + :marked + The parentheses identify the `
  • ` element’s `click` event as the target. + The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`, + passing the local template variable `hero` as an argument. + That’s the same `hero` variable we defined previously in the `ngFor`. + .l-sub-section + :marked + Learn more about Event Binding in the + [User Input](../guide/user-input.html) and + [Templating Syntax](../guide/template-syntax.html#event-binding) chapters. + :marked + ### Add the click handler + Our event binding refers to an `onSelect` method that doesn’t exist yet. + We’ll add that method to our component now. + + What should that method do? It should set the component’s selected hero to the hero that the user clicked. + + Our component doesn’t have a “selected hero” yet either. We’ll start there. + + ### Expose the selected hero + We no longer need the static `hero` property of the `AppComponent`. + **Replace** it with this simple `selectedHero` property: + + +makeExample('toh-2/dart/lib/app_component.dart', 'selected-hero-1', 'app_component.dart (selectedHero)') + + :marked + We’ve decided that none of the heroes should be selected before the user picks a hero so + we won’t initialize the `selectedHero` as we were doing with `hero`. + + Now **add an `onSelect` method** that sets the `selectedHero` property to the `hero` the user clicked. + +makeExample('toh-2/dart/lib/app_component.dart', 'on-select-1', 'app_component.dart (onSelect)')(format=".") + + :marked + We will be showing the selected hero's details in our template. + At the moment, it is still referring to the old `hero` property. + Let’s fix the template to bind to the new `selectedHero` property. + + +makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'selectedHero-details', 'app_component.dart (Binding to the selectedHero\'s name)')(format=".") + :marked + ### Hide the empty detail with ngIf + + When our app loads we see a list of heroes, but a hero is not selected. + The `selectedHero` is `undefined`. + That’s why we'll see the following error in the browser’s console: + + code-example(language="html"). + EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null] + + :marked + Remember that we are displaying `selectedHero.name` in the template. + This name property does not exist because `selectedHero` itself is undefined. + + We'll address this problem by keeping the hero detail out of the DOM until there is a selected hero. + + We wrap the HTML hero detail content of our template with a `
    `. + Then we add the `ngIf` built-in directive and set it to the `selectedHero` property of our component. + + +makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'ng-if', 'app_component.dart (ngIf)')(format=".") + + .alert.is-critical + :marked + Remember that the leading asterisk (`*`) in front of `ngIf` is + a critical part of this syntax. + :marked + When there is no `selectedHero`, the `ngIf` directive removes the hero detail HTML from the DOM. + There will be no hero detail elements and no bindings to worry about. + + When the user picks a hero, `selectedHero` isn't `null` anymore and + `ngIf` puts the hero detail content into the DOM and evaluates the nested bindings. + .l-sub-section + :marked + `ngIf` and `ngFor` are called “structural directives” because they can change the + structure of portions of the DOM. + In other words, they give structure to the way Angular displays content in the DOM. + + Learn more about `ngIf`, `ngFor` and other structural directives in the + [Structural Directives](../guide/structural-directives.html) and + [Template Syntax](../guide/template-syntax.html#directives) chapters. + + :marked + The browser refreshes and we see the list of heroes but not the selected hero detail. + The `ngIf` keeps it out of the DOM as long as the `selectedHero` is undefined. + When we click on a hero in the list, the selected hero displays in the hero details. + Everything is working as we expect. + + ### Styling the selection + + We see the selected hero in the details area below but we can’t quickly locate that hero in the list above. + We can fix that by applying the `selected` CSS class to the appropriate `
  • ` in the master list. + For example, when we select Magneta from the heroes list, + we can make it pop out visually by giving it a subtle background color as shown here. + + figure.image-display + img(src='/resources/images/devguide/toh/heroes-list-selected.png' alt="Selected hero") + :marked + We’ll add a property binding on `class` for the `selected` class to the template. We'll set this to an expression that compares the current `selectedHero` to the `hero`. + + The key is the name of the CSS class (`selected`). The value is `true` if the two heroes match and `false` otherwise. + We’re saying “*apply the `selected` class if the heroes match, remove it if they don’t*”. + +makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'class-selected-1', 'app_component.dart (Setting the CSS class)')(format=".") + :marked + Notice in the template that the `class.selected` is surrounded in square brackets (`[]`). + This is the syntax for a Property Binding, a binding in which data flows one way + from the data source (the expression `hero == selectedHero`) to a property of `class`. + +makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'class-selected-2', 'app_component.dart (Styling each hero)')(format=".") + + .l-sub-section + :marked + Learn more about [Property Binding](../guide/template-syntax.html#property-binding) + in the Template Syntax chapter. + + :marked + The browser reloads our app. + We select the hero Magneta and the selection is clearly identified by the background color. + + figure.image-display + img(src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app") + + :marked + We select a different hero and the tell-tale color switches to that hero. + + Here's the complete `app_component.dart` as it stands now: + + +makeExample('toh-2/dart/lib/app_component.dart', 'pt2', 'app_component.dart') + +.l-main-section +:marked + ## The Road We’ve Travelled + Here’s what we achieved in this chapter: + + * Our Tour of Heroes now displays a list of selectable heroes + * We added the ability to select a hero and show the hero’s details + * We learned how to use the built-in directives `ngIf` and `ngFor` in a component’s template + + ### The Road Ahead + Our Tour of Heroes has grown, but it’s far from complete. + We can't put the entire app into a single component. + We need to break it up into sub-components and teach them to work together + as we learn in the [next chapter](toh-pt3.html).