diff --git a/harp.json b/harp.json index ade3edf78e..02aec61d19 100644 --- a/harp.json +++ b/harp.json @@ -266,6 +266,15 @@ "bio": "Scott works for Google on the Material Design team, where he brings designers' dreams to life on the web.", "type": "Google" }, + + "kara": { + "name": "Kara Erickson", + "picture": "/resources/images/bios/kara-erickson.jpg", + "twitter": "karaforthewin", + "bio": "Kara is a software engineer on the Angular team at Google and a co-organizer of the Angular-SF Meetup. Prior to Google, she helped build UI components in Angular for guest management systems at OpenTable. She enjoys snacking indiscriminately and probably other things too.", + "type": "Google" + }, + "pawel": { "name": "Pawel Kozlowski", "picture": "/resources/images/bios/pawel.jpg", diff --git a/public/_data.json b/public/_data.json index de70c1662a..f34f811f46 100644 --- a/public/_data.json +++ b/public/_data.json @@ -14,5 +14,15 @@ "title": "Contribute to Angular", "subtitle": "Help us build the framework of the future!", "autoformat": "true" + }, + + "news": { + "title": "News", + "subtitle": "Check out what we are up to" + }, + + "support": { + "title": "Support", + "subtitle": "Get help from the Angular Community" } -} \ No newline at end of file +} diff --git a/public/_includes/_main-nav.jade b/public/_includes/_main-nav.jade index d851f83d45..16ba8719cb 100644 --- a/public/_includes/_main-nav.jade +++ b/public/_includes/_main-nav.jade @@ -9,4 +9,6 @@ md-toolbar(class="main-nav background-regal l-pinned-top l-layer-5",scroll-y-off li.l-left Docs li.l-left About li.l-left Contribute - li.l-right.feedback-button feedback \ No newline at end of file + li.l-left Support + li.l-left News + li.l-right.feedback-button feedback diff --git a/public/_includes/_scripts-include.jade b/public/_includes/_scripts-include.jade index 20d59ae707..b916ec74ef 100644 --- a/public/_includes/_scripts-include.jade +++ b/public/_includes/_scripts-include.jade @@ -49,4 +49,4 @@ if current.path[0] == "docs" _st('install','VsuU7kH5Hnnj9tfyNvfK'); -script(src="//www.gstatic.com/feedback/api.js" type="text/javascript") \ No newline at end of file +script(src="//www.gstatic.com/feedback/api.js" type="text/javascript") diff --git a/public/docs/_examples/hierarchical-dependency-injection/dart/lib/edit_item.dart b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/edit_item.dart new file mode 100644 index 0000000000..c5de35a9ed --- /dev/null +++ b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/edit_item.dart @@ -0,0 +1,7 @@ +// #docregion +class EditItem { + bool editing = false; + T item; + EditItem(this.item); +} +// #enddocregion diff --git a/public/docs/_examples/hierarchical-dependency-injection/dart/lib/hero.dart b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/hero.dart new file mode 100644 index 0000000000..3a9ec96783 --- /dev/null +++ b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/hero.dart @@ -0,0 +1,14 @@ +// #docregion +import 'package:angular2/angular2.dart'; + +class Hero { + String name; + String power; + + Hero clone() { + return new Hero() + ..name = name + ..power = power; + } +} +// #enddocregion diff --git a/public/docs/_examples/hierarchical-dependency-injection/dart/lib/hero_card_component.dart b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/hero_card_component.dart new file mode 100644 index 0000000000..1506813bbc --- /dev/null +++ b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/hero_card_component.dart @@ -0,0 +1,16 @@ +// #docregion +import 'package:angular2/angular2.dart'; +import 'package:hierarchical_di/hero.dart'; + +@Component( + selector: 'hero-card', + template: ''' +
+ Name: + {{hero.name}} +
+ ''') +class HeroCardComponent { + @Input() Hero hero; +} +// #docregion diff --git a/public/docs/_examples/hierarchical-dependency-injection/dart/lib/hero_editor_component.dart b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/hero_editor_component.dart new file mode 100644 index 0000000000..0ecb02a840 --- /dev/null +++ b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/hero_editor_component.dart @@ -0,0 +1,47 @@ +// #docregion +import 'package:angular2/angular2.dart'; +import 'package:hierarchical_di/restore_service.dart'; +import 'package:hierarchical_di/hero.dart'; + +@Component( + selector: 'hero-editor', + // #docregion providers + providers: const [RestoreService], + // #enddocregion providers + template: ''' +
+ Name: + +
+ + +
+
+ ''') +class HeroEditorComponent { + @Output() final EventEmitter canceled = new EventEmitter(); + @Output() final EventEmitter saved = new EventEmitter(); + + RestoreService _restoreService; + + HeroEditorComponent(this._restoreService); + + @Input() + set hero(Hero hero) { + _restoreService.setItem(hero); + } + + Hero get hero { + return _restoreService.getItem(); + } + + onSaved() { + saved.add(_restoreService.getItem()); + } + + onCanceled() { + hero = _restoreService.restoreItem(); + canceled.add(hero); + } +} +// #enddocregion diff --git a/public/docs/_examples/hierarchical-dependency-injection/dart/lib/heroes_list_component.dart b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/heroes_list_component.dart new file mode 100644 index 0000000000..564f2e203b --- /dev/null +++ b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/heroes_list_component.dart @@ -0,0 +1,53 @@ +// #docregion +import 'package:angular2/angular2.dart'; +import 'package:hierarchical_di/hero.dart'; +import 'package:hierarchical_di/heroes_service.dart'; +import 'package:hierarchical_di/hero_editor_component.dart'; +import 'package:hierarchical_di/hero_card_component.dart'; +import 'package:hierarchical_di/edit_item.dart'; + +@Component( + selector: 'heroes-list', + template: ''' +
+
    +
  • + + + + + +
  • +
+
+ ''', + directives: const [HeroCardComponent, HeroEditorComponent]) +class HeroesListComponent { + List> heroes; + HeroesListComponent(HeroesService heroesService) { + heroes = heroesService + .getHeroes() + .map((Hero item) => new EditItem(item)) + .toList(); + } + + onSaved(EditItem editItem, Hero updatedHero) { + editItem.item = updatedHero; + editItem.editing = false; + } + + onCanceled(EditItem editItem) { + editItem.editing = false; + } +} +// #enddocregion diff --git a/public/docs/_examples/hierarchical-dependency-injection/dart/lib/heroes_service.dart b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/heroes_service.dart new file mode 100644 index 0000000000..de9508ec0a --- /dev/null +++ b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/heroes_service.dart @@ -0,0 +1,18 @@ +import 'package:angular2/angular2.dart'; +import 'package:hierarchical_di/hero.dart'; + +@Injectable() +class HeroesService { + List _heroes = [ + new Hero() + ..name = "RubberMan" + ..power = 'Flexibility', + new Hero() + ..name = "Tornado" + ..power = 'Weather changer' + ]; + + List getHeroes() { + return _heroes; + } +} diff --git a/public/docs/_examples/hierarchical-dependency-injection/dart/lib/restore_service.dart b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/restore_service.dart new file mode 100644 index 0000000000..a6d8c59d35 --- /dev/null +++ b/public/docs/_examples/hierarchical-dependency-injection/dart/lib/restore_service.dart @@ -0,0 +1,29 @@ +// #docregion +import 'package:angular2/angular2.dart'; + +@Injectable() +class RestoreService { + T _originalItem; + T _currentItem; + + setItem(T item) { + print(item.runtimeType); + _originalItem = item; + _currentItem = clone(item); + } + + T getItem() { + return _currentItem; + } + + T restoreItem() { + _currentItem = _originalItem; + return getItem(); + } + + T clone(T item) { + // super poor clone implementation + return item.clone(); + } +} +// #enddocregion diff --git a/public/docs/_examples/hierarchical-dependency-injection/dart/pubspec.yaml b/public/docs/_examples/hierarchical-dependency-injection/dart/pubspec.yaml new file mode 100644 index 0000000000..d7822d6b1c --- /dev/null +++ b/public/docs/_examples/hierarchical-dependency-injection/dart/pubspec.yaml @@ -0,0 +1,19 @@ +# #docregion +name: 'hierarchical_di' +version: 0.0.1 +description: hierarchical dependency injection example + +environment: + sdk: '>=1.0.0 <2.0.0' + +dependencies: + angular2: '2.0.0-beta.1' + browser: ^0.10.0 + dart_to_js_script_rewriter: '^0.1.0' + +transformers: +- angular2: + platform_directives: + - 'package:angular2/common.dart#COMMON_DIRECTIVES' + entry_points: web/main.dart +- dart_to_js_script_rewriter diff --git a/public/docs/_examples/hierarchical-dependency-injection/dart/web/index.html b/public/docs/_examples/hierarchical-dependency-injection/dart/web/index.html new file mode 100644 index 0000000000..cd76cf209b --- /dev/null +++ b/public/docs/_examples/hierarchical-dependency-injection/dart/web/index.html @@ -0,0 +1,19 @@ + + + + + + + + Hierarchical Injector + + + + + + + loading... + + + + diff --git a/public/docs/_examples/hierarchical-dependency-injection/dart/web/main.dart b/public/docs/_examples/hierarchical-dependency-injection/dart/web/main.dart new file mode 100644 index 0000000000..bfd8d2c649 --- /dev/null +++ b/public/docs/_examples/hierarchical-dependency-injection/dart/web/main.dart @@ -0,0 +1,15 @@ +// #docregion +import 'package:angular2/bootstrap.dart'; +import 'package:hierarchical_di/heroes_service.dart'; +import 'package:hierarchical_di/heroes_list_component.dart'; + +void main() { + bootstrap(HeroesListComponent, [HeroesService]); +} + +/* Documentation artifact below +// #docregion bad-alternative +// Don't do this! +bootstrap(HeroesListComponent, [HeroesService, RestoreService]) +// #enddocregion bad-alternative +*/ diff --git a/public/docs/_examples/template-syntax/ts/app/app.component.html b/public/docs/_examples/template-syntax/ts/app/app.component.html index d9c7aba1ce..5a567265fb 100644 --- a/public/docs/_examples/template-syntax/ts/app/app.component.html +++ b/public/docs/_examples/template-syntax/ts/app/app.component.html @@ -255,7 +255,10 @@

+ +
click with myClick
+ {{clickity}}
@@ -382,8 +385,8 @@

Use setStyles2() - camelCase style property names

This div is italic, normal weight, and x-large -
- After setStyles2(), the styles are "{{getStyles(styleDiv)}}" +
+ After setStyles2(), the styles are "{{getStyles(styleDiv2)}}"
@@ -445,7 +448,7 @@

Use setStyles2() - camelCase style property names

Pick a toe
You picked - + @@ -453,7 +456,7 @@

Use setStyles2() - camelCase style property names

- +
@@ -477,8 +480,8 @@

Use setStyles2() - camelCase style property names


- +
{{i + 1}} - {{hero.fullName}}
diff --git a/public/docs/_examples/template-syntax/ts/app/app.component.ts b/public/docs/_examples/template-syntax/ts/app/app.component.ts index 27436547f2..28da87436e 100644 --- a/public/docs/_examples/template-syntax/ts/app/app.component.ts +++ b/public/docs/_examples/template-syntax/ts/app/app.component.ts @@ -1,3 +1,5 @@ +//#docplaster + import {Component} from 'angular2/core'; import {NgForm} from 'angular2/common'; @@ -37,6 +39,11 @@ export class AppComponent { currentHero = Hero.MockHeroes[0]; + // DevMode memoization fields + private _priorClasses:{}; + private _priorStyles:{}; + private _priorStyles2:{}; + getStyles(el:Element){ let styles = window.getComputedStyle(el); let showStyles = {}; @@ -100,36 +107,61 @@ export class AppComponent { // #docregion setClasses setClasses() { - return { + let classes = { saveable: this.canSave, // true modified: !this.isUnchanged, // false special: this.isSpecial, // true } + // #enddocregion setClasses + // compensate for DevMode (sigh) + if (JSON.stringify(classes) === JSON.stringify(this._priorClasses)){ + return this._priorClasses; + } + this._priorClasses = classes; + // #docregion setClasses + return classes; } // #enddocregion setClasses + // #docregion setStyles setStyles() { - return { + let styles = { // CSS property names 'font-style': this.canSave ? 'italic' : 'normal', // italic 'font-weight': !this.isUnchanged ? 'bold' : 'normal', // normal 'font-size': this.isSpecial ? 'x-large': 'smaller', // larger } + // #enddocregion setStyles + // compensate for DevMode (sigh) + if (JSON.stringify(styles) === JSON.stringify(this._priorStyles)){ + return this._priorStyles; + } + this._priorStyles = styles; + // #docregion setStyles + return styles; } // #enddocregion setStyles - + // #docregion setStyles2 setStyles2() { - return { + let styles = { // camelCase style properties work too fontStyle: this.canSave ? 'italic' : 'normal', // italic fontWeight: !this.isUnchanged ? 'bold' : 'normal', // normal fontSize: this.isSpecial ? 'x-large': 'smaller', // larger } + // #enddocregion setStyles2 + // compensate for DevMode (sigh) + if (JSON.stringify(styles) === JSON.stringify(this._priorStyles2)){ + return this._priorStyles2; + } + this._priorStyles2 = styles; + // #docregion setStyles2 + return styles; } // #enddocregion setStyles2 - + toeChoice = ''; toeChooser(picker:HTMLFieldSetElement){ let choices = picker.children; diff --git a/public/docs/_examples/template-syntax/ts/app/my-click.directive.ts b/public/docs/_examples/template-syntax/ts/app/my-click.directive.ts index 8f52f9953c..88901da46b 100644 --- a/public/docs/_examples/template-syntax/ts/app/my-click.directive.ts +++ b/public/docs/_examples/template-syntax/ts/app/my-click.directive.ts @@ -1,7 +1,7 @@ // #docplaster import {Directive, Output, ElementRef, EventEmitter} from 'angular2/core'; -@Directive({selector:'[mClick]'}) +@Directive({selector:'[myClick]'}) export class MyClickDirective { // #docregion my-click-output-1 @Output('myClick') clicks = new EventEmitter(); diff --git a/public/docs/_examples/toh-1/dart-snippets/app_component_snippets_pt1.dart b/public/docs/_examples/toh-1/dart-snippets/app_component_snippets_pt1.dart new file mode 100644 index 0000000000..1a33bbd069 --- /dev/null +++ b/public/docs/_examples/toh-1/dart-snippets/app_component_snippets_pt1.dart @@ -0,0 +1,43 @@ +// #docregion show-hero +template: '

{{title}}

{{hero}} details!

' +// #enddocregion show-hero + +// #docregion show-hero-2 +template: '

{{title}}

{{hero.name}} details!

' +// #enddocregion show-hero-2 + +// #docregion show-hero-properties +template: '

{{title}}

{{hero.name}} details!

{{hero.id}}
{{hero.name}}
' +// #enddocregion show-hero-properties + +// #docregion multi-line-strings +template: ''' +

{{title}}

+

{{hero.name}} details!

+
{{hero.id}}
+
{{hero.name}}
+ ''' +// #enddocregion multi-line-strings + +// #docregion editing-Hero +template:''' +

{{title}}

+

{{hero.name}} details!

+
{{hero.id}}
+
+ +
+
+ ''' +// #enddocregion editing-Hero + +// #docregion app-component-1 +class AppComponent { + String title = 'Tour of Heroes'; + Hero hero = 'Windstorm'; +} +// #enddocregion app-component-1 + +// #docregion hero-property-1 +Hero hero = new Hero(1, 'Windstorm'); +// #enddocregion hero-property-1 diff --git a/public/docs/_examples/toh-1/dart/lib/app_component.dart b/public/docs/_examples/toh-1/dart/lib/app_component.dart new file mode 100644 index 0000000000..a7a1e95f4b --- /dev/null +++ b/public/docs/_examples/toh-1/dart/lib/app_component.dart @@ -0,0 +1,29 @@ +// #docregion pt1 +import 'package:angular2/core.dart'; + +// #docregion hero-class-1 +class Hero { + final int id; + String name; + + Hero(this.id, this.name); +} +// #enddocregion hero-class-1 + +@Component( + selector: 'my-app', + template: ''' +

{{title}}

+

{{hero.name}} details!

+
{{hero.id}}
+
+ +
+
+ ''') +class AppComponent { + String title = 'Tour of Heroes'; + Hero hero = new Hero(1, 'Windstorm'); +} + +// #enddocregion pt1 diff --git a/public/docs/_examples/toh-1/dart/pubspec.yaml b/public/docs/_examples/toh-1/dart/pubspec.yaml new file mode 100644 index 0000000000..ddfafde27c --- /dev/null +++ b/public/docs/_examples/toh-1/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-1/dart/web/index.html b/public/docs/_examples/toh-1/dart/web/index.html new file mode 100644 index 0000000000..059daeed4c --- /dev/null +++ b/public/docs/_examples/toh-1/dart/web/index.html @@ -0,0 +1,10 @@ + + + + + + + + Loading... + + diff --git a/public/docs/_examples/toh-1/dart/web/main.dart b/public/docs/_examples/toh-1/dart/web/main.dart new file mode 100644 index 0000000000..d5b50195c1 --- /dev/null +++ b/public/docs/_examples/toh-1/dart/web/main.dart @@ -0,0 +1,9 @@ +// #docregion pt1 +import 'package:angular2/bootstrap.dart'; + +import 'package:angular2_tour_of_heroes/app_component.dart'; + +main() { + bootstrap(AppComponent); +} +// #enddocregion pt1 \ No newline at end of file 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

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    +// #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..3459b994a4 --- /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

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    +
    +

    {{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..059daeed4c --- /dev/null +++ b/public/docs/_examples/toh-2/dart/web/index.html @@ -0,0 +1,10 @@ + + + + + + + + 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/_examples/toh-3/dart/lib/app_component.dart b/public/docs/_examples/toh-3/dart/lib/app_component.dart new file mode 100644 index 0000000000..54df64293a --- /dev/null +++ b/public/docs/_examples/toh-3/dart/lib/app_component.dart @@ -0,0 +1,102 @@ +//#docregion +import 'package:angular2/angular2.dart'; +// #docregion hero-import +import 'hero.dart'; +// #enddocregion hero-import +// #docregion hero-detail-import +import 'hero_detail_component.dart'; +// #enddocregion hero-detail-import + +@Component( + selector: 'my-app', +// #docregion hero-detail-template + template: ''' +

    {{title}}

    +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    + + ''', +// #enddocregion hero-detail-template + 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; + } + ''' + ], +// #docregion directives + directives: const [ + HeroDetailComponent + ]) +// #enddocregion directives +class AppComponent { + final String title = 'Tour of Heroes'; + final List heroes = mockHeroes; + Hero selectedHero; + + onSelect(Hero hero) { + selectedHero = hero; + } +} + +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") +]; diff --git a/public/docs/_examples/toh-3/dart/lib/hero.dart b/public/docs/_examples/toh-3/dart/lib/hero.dart new file mode 100644 index 0000000000..71eb336b9c --- /dev/null +++ b/public/docs/_examples/toh-3/dart/lib/hero.dart @@ -0,0 +1,8 @@ +// #docregion +class Hero { + final int id; + String name; + + Hero(this.id, this.name); +} +// #enddocregion diff --git a/public/docs/_examples/toh-3/dart/lib/hero_detail_component.dart b/public/docs/_examples/toh-3/dart/lib/hero_detail_component.dart new file mode 100644 index 0000000000..c7e1b87d52 --- /dev/null +++ b/public/docs/_examples/toh-3/dart/lib/hero_detail_component.dart @@ -0,0 +1,38 @@ +// #docplaster +// #docregion +// #docregion v1 +import 'package:angular2/core.dart'; +// #enddocregion v1 +// #docregion hero-import +import 'hero.dart'; +// #enddocregion hero-import + +// #docregion v1 +@Component( + selector: 'my-hero-detail', +// #enddocregion v1 + // #docregion template + template: ''' +
    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    + + +
    +
    + ''', + // #enddocregion template + // #docregion inputs + inputs: const ['hero'] + // #enddocregion inputs +// #docregion v1 +) +class HeroDetailComponent { +// #enddocregion v1 +// #docregion hero + Hero hero; +// #enddocregion hero +// #docregion v1 +} +// #enddocregion v1 diff --git a/public/docs/_examples/toh-3/dart/pubspec.yaml b/public/docs/_examples/toh-3/dart/pubspec.yaml new file mode 100644 index 0000000000..ddfafde27c --- /dev/null +++ b/public/docs/_examples/toh-3/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-3/dart/web/index.html b/public/docs/_examples/toh-3/dart/web/index.html new file mode 100644 index 0000000000..059daeed4c --- /dev/null +++ b/public/docs/_examples/toh-3/dart/web/index.html @@ -0,0 +1,10 @@ + + + + + + + + Loading... + + diff --git a/public/docs/_examples/toh-3/dart/web/main.dart b/public/docs/_examples/toh-3/dart/web/main.dart new file mode 100644 index 0000000000..d5b50195c1 --- /dev/null +++ b/public/docs/_examples/toh-3/dart/web/main.dart @@ -0,0 +1,9 @@ +// #docregion pt1 +import 'package:angular2/bootstrap.dart'; + +import 'package:angular2_tour_of_heroes/app_component.dart'; + +main() { + bootstrap(AppComponent); +} +// #enddocregion pt1 \ No newline at end of file diff --git a/public/docs/_examples/toh-4/dart/lib/app_component.dart b/public/docs/_examples/toh-4/dart/lib/app_component.dart new file mode 100644 index 0000000000..ce8251dbc2 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/app_component.dart @@ -0,0 +1,106 @@ +// #docplaster +// #docregion +import 'dart:async'; + +import 'package:angular2/core.dart'; + +import 'hero.dart'; +import 'hero_detail_component.dart'; +// #docregion hero-service-import +import 'hero_service.dart'; + +// #enddocregion hero-service-import + +@Component( + selector: 'my-app', + template: ''' +

    {{title}}

    +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    + + ''', + 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; + } + ''' + ], + directives: const [ + HeroDetailComponent + ], + providers: const [ + HeroService + ]) +class AppComponent implements OnInit { + String title = 'Tour of Heroes'; + List heroes; + Hero selectedHero; + + final HeroService _heroService; + + AppComponent(this._heroService); + +// #docregion get-heroes + getHeroes() async { + heroes = await _heroService.getHeroes(); + } +// #enddocregion get-heroes + + ngOnInit() { + getHeroes(); + } + + onSelect(Hero hero) { + selectedHero = hero; + } +} diff --git a/public/docs/_examples/toh-4/dart/lib/app_component_1.dart b/public/docs/_examples/toh-4/dart/lib/app_component_1.dart new file mode 100644 index 0000000000..2f2cbb0dd0 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/app_component_1.dart @@ -0,0 +1,64 @@ +// #docplaster +// #docregion on-init +import 'package:angular2/core.dart'; + +// #enddocregion on-init +import 'hero.dart'; +import 'hero_detail_component.dart'; +// #docregion hero-service-import +import 'hero_service_1.dart'; +// #enddocregion hero-service-import + +// Testable but never shown +@Component( + selector: 'my-app', + template: ''' +
    + {{hero.name}} +
    + + ''', + directives: const [HeroDetailComponent], + // #docregion providers + providers: const [HeroService]) +// #enddocregion providers +// #docregion on-init +class AppComponent implements OnInit { + // #enddocregion on-init + String title = 'Tour of Heroes'; + // #docregion heroes-prop + List heroes; + // #enddocregion heroes-prop + Hero selectedHero; + + // #docregion new-service + HeroService heroService = new HeroService(); // don't do this + // #enddocregion new-service + // #docregion ctor + final HeroService _heroService; + AppComponent(this._heroService); + // #enddocregion ctor + // #docregion getHeroes + getHeroes() { + //#docregion get-heroes + heroes = _heroService.getHeroes(); + // #enddocregion get-heroes + } + // #enddocregion getHeroes + + // #docregion ng-on-init + // #docregion on-init + ngOnInit() { + // #enddocregion on-init + getHeroes(); + // #docregion on-init + } + // #enddocregion on-init + // #enddocregion ng-on-init + + onSelect(Hero hero) { + selectedHero = hero; + } + // #docregion on-init +} +// #enddocregion on-init diff --git a/public/docs/_examples/toh-4/dart/lib/hero.dart b/public/docs/_examples/toh-4/dart/lib/hero.dart new file mode 100644 index 0000000000..828f8cebab --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/hero.dart @@ -0,0 +1,6 @@ +class Hero { + final int id; + String name; + + Hero(this.id, this.name); +} diff --git a/public/docs/_examples/toh-4/dart/lib/hero_detail_component.dart b/public/docs/_examples/toh-4/dart/lib/hero_detail_component.dart new file mode 100644 index 0000000000..5d1f76fac8 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/hero_detail_component.dart @@ -0,0 +1,19 @@ +import 'package:angular2/core.dart'; +import 'hero.dart'; + +@Component( + selector: 'my-hero-detail', + template: ''' +
    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    + + +
    +
    + ''', + inputs: const ['hero']) +class HeroDetailComponent { + Hero hero; +} diff --git a/public/docs/_examples/toh-4/dart/lib/hero_service.dart b/public/docs/_examples/toh-4/dart/lib/hero_service.dart new file mode 100644 index 0000000000..4893495822 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/hero_service.dart @@ -0,0 +1,23 @@ +// #docplaster +// #docregion +import 'dart:async'; + +import 'package:angular2/core.dart'; + +import 'mock_heroes.dart'; +import 'hero.dart'; + +@Injectable() +class HeroService { + //#docregion get-heroes + Future> getHeroes() => new Future(() => mockHeroes); + //#enddocregion get-heroes + + // See the "Take it slow" appendix + //#docregion get-heroes-slowly + Future> getHeroesSlowly() { + return new Future.delayed(const Duration(seconds: 2), () => mockHeroes); + } + //#enddocregion get-heroes-slowly +} +// #enddocregion diff --git a/public/docs/_examples/toh-4/dart/lib/hero_service_1.dart b/public/docs/_examples/toh-4/dart/lib/hero_service_1.dart new file mode 100644 index 0000000000..76ecb2143f --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/hero_service_1.dart @@ -0,0 +1,21 @@ +// #docplaster +// #docregion +import 'mock_heroes.dart'; +import 'hero.dart'; +// #docregion empty-class +import 'package:angular2/core.dart'; + +// #docregion getHeroes-stub +@Injectable() +class HeroService { +// #enddocregion empty-class + List getHeroes() { +// #enddocregion getHeroes-stub + return mockHeroes; +// #docregion getHeroes-stub + } +// #docregion empty-class +} +// #enddocregion getHeroes-stub +// #enddocregion empty-class +// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/toh-4/dart/lib/mock_heroes.dart b/public/docs/_examples/toh-4/dart/lib/mock_heroes.dart new file mode 100644 index 0000000000..55434761a2 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/mock_heroes.dart @@ -0,0 +1,16 @@ +// #docregion +import 'hero.dart'; + +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 \ No newline at end of file diff --git a/public/docs/_examples/toh-4/dart/pubspec.yaml b/public/docs/_examples/toh-4/dart/pubspec.yaml new file mode 100644 index 0000000000..ddfafde27c --- /dev/null +++ b/public/docs/_examples/toh-4/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-4/dart/web/index.html b/public/docs/_examples/toh-4/dart/web/index.html new file mode 100644 index 0000000000..059daeed4c --- /dev/null +++ b/public/docs/_examples/toh-4/dart/web/index.html @@ -0,0 +1,10 @@ + + + + + + + + Loading... + + diff --git a/public/docs/_examples/toh-4/dart/web/main.dart b/public/docs/_examples/toh-4/dart/web/main.dart new file mode 100644 index 0000000000..d5b50195c1 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/web/main.dart @@ -0,0 +1,9 @@ +// #docregion pt1 +import 'package:angular2/bootstrap.dart'; + +import 'package:angular2_tour_of_heroes/app_component.dart'; + +main() { + bootstrap(AppComponent); +} +// #enddocregion pt1 \ No newline at end of file diff --git a/public/docs/_examples/toh-4/dart/web/main_1.dart b/public/docs/_examples/toh-4/dart/web/main_1.dart new file mode 100644 index 0000000000..c8baa39ed2 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/web/main_1.dart @@ -0,0 +1,9 @@ +// #docregion pt1 +import 'package:angular2/bootstrap.dart'; + +import 'package:angular2_tour_of_heroes/app_component_1.dart'; + +main() { + bootstrap(AppComponent); +} +// #enddocregion pt1 \ No newline at end of file diff --git a/public/docs/_examples/tutorial/dart/.gitignore b/public/docs/_examples/tutorial/dart/.gitignore new file mode 100644 index 0000000000..c217c48cee --- /dev/null +++ b/public/docs/_examples/tutorial/dart/.gitignore @@ -0,0 +1,22 @@ +# Files and directories created by pub +.packages +.pub/ +build/ +packages +pubspec.lock # (Remove this pattern if you wish to check in your lock file) + +# Files created by dart2js +*.dart.js +*.part.js +*.js.deps +*.js.map +*.info.json + +# Directory created by dartdoc +doc/api/ + +# JetBrains IDEs +.idea/ +*.iml +*.ipr +*.iws diff --git a/public/docs/_examples/tutorial/dart/lib/app_component.css b/public/docs/_examples/tutorial/dart/lib/app_component.css new file mode 100644 index 0000000000..5800fecdb0 --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/app_component.css @@ -0,0 +1,28 @@ +a { + padding: 5px 10px; + text-decoration: none; + margin-top: 10px; + display: inline-block; + background-color: #eee; + border-radius: 4px; +} +a:visited, a:link { + color: #607D8B; +} +a:hover { + color: #039be5; + background-color: #CFD8DC; +} +a.router-link-active { + color: #039be5; +} +h1 { + font-size: 1.2em; + color: #999; + margin-bottom: 0; +} +h2 { + font-size: 2em; + margin-top: 0; + padding-top: 0; +} \ No newline at end of file diff --git a/public/docs/_examples/tutorial/dart/lib/app_component.dart b/public/docs/_examples/tutorial/dart/lib/app_component.dart new file mode 100644 index 0000000000..aa1c8588e2 --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/app_component.dart @@ -0,0 +1,32 @@ +import 'package:angular2/core.dart'; +import 'package:angular2/router.dart'; + +import 'heroes_component.dart'; +import 'hero_detail_component.dart'; +import 'dashboard_component.dart'; +import 'hero_service.dart'; + +@Component( + selector: 'my-app', + template: ''' +

    {{title}}

    + Dashboard + Heroes + + ''', + styleUrls: const ['app_component.css'], + directives: const [ROUTER_DIRECTIVES], + providers: const [HeroService]) +@RouteConfig(const [ + const Route( + path: '/dashboard', + name: 'Dashboard', + component: DashboardComponent, + useAsDefault: true), + const Route(path: '/heroes', name: 'Heroes', component: HeroesComponent), + const Route( + path: '/detail/:id', name: 'HeroDetail', component: HeroDetailComponent) +]) +class AppComponent { + final String title = 'Tour of Heroes'; +} diff --git a/public/docs/_examples/tutorial/dart/lib/dashboard_component.css b/public/docs/_examples/tutorial/dart/lib/dashboard_component.css new file mode 100644 index 0000000000..51968caa0b --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/dashboard_component.css @@ -0,0 +1,60 @@ +[class*='col-'] { + float: left; +} +*, *:after, *:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +h3 { + text-align: center; margin-bottom: 0; +} +[class*='col-'] { + padding-right: 20px; + padding-bottom: 20px; +} +[class*='col-']:last-of-type { + padding-right: 0; +} +.grid { + margin: 0 0em; +} +.col-1-4 { + width: 25%; +} +.module { + padding: 20px; + text-align: center; + color: #eee; + max-height: 120px; + min-width: 120px; + background-color: #607D8B; + border-radius: 2px; +} +h4 { + position: relative; +} +.module:hover { + background-color: #EEE; + cursor: pointer; + color: #607d8b; +} +.grid-pad { + padding: 10px 0; +} +.grid-pad > [class*='col-']:last-of-type { + padding-right: 20px; +} +@media (max-width: 600px) { + .module { + font-size: 10px; + max-height: 75px; } +} +@media (max-width: 1024px) { + .grid { + margin: 0; + } + .module { + min-width: 60px; + } +} diff --git a/public/docs/_examples/tutorial/dart/lib/dashboard_component.dart b/public/docs/_examples/tutorial/dart/lib/dashboard_component.dart new file mode 100644 index 0000000000..fbb45f6f9d --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/dashboard_component.dart @@ -0,0 +1,31 @@ +import 'package:angular2/core.dart'; +import 'package:angular2/router.dart'; + +import 'hero.dart'; +import 'hero_service.dart'; + +@Component( + selector: 'my-dashboard', + templateUrl: 'dashboard_component.html', + styleUrls: const ['dashboard_component.css']) +class DashboardComponent implements OnInit { + List heroes = []; + + final HeroService _heroService; + final Router _router; + DashboardComponent(this._heroService, this._router); + + ngOnInit() async { + _heroService + .getHeroes() + .then((heroes) => this.heroes = heroes.sublist(1, 5)); + } + + gotoDetail(Hero hero) { + _router.navigate([ + 'HeroDetail', + {'id': hero.id} + ]); + } +} + diff --git a/public/docs/_examples/tutorial/dart/lib/dashboard_component.html b/public/docs/_examples/tutorial/dart/lib/dashboard_component.html new file mode 100644 index 0000000000..f9b9b2f3bb --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/dashboard_component.html @@ -0,0 +1,8 @@ +

    Top Heroes

    +
    +
    +
    +

    {{hero.name}}

    +
    +
    +
    diff --git a/public/docs/_examples/tutorial/dart/lib/hero.dart b/public/docs/_examples/tutorial/dart/lib/hero.dart new file mode 100644 index 0000000000..828f8cebab --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/hero.dart @@ -0,0 +1,6 @@ +class Hero { + final int id; + String name; + + Hero(this.id, this.name); +} diff --git a/public/docs/_examples/tutorial/dart/lib/hero_detail_component.css b/public/docs/_examples/tutorial/dart/lib/hero_detail_component.css new file mode 100644 index 0000000000..18fa6cb309 --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/hero_detail_component.css @@ -0,0 +1,24 @@ +label { + display: inline-block; + width: 3em; + margin: .5em 0; + color: #607D8B; + font-weight: bold; +} +input { + height: 2em; + font-size: 1em; + padding-left: .4em; +} +button { + margin-top: 20px; + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} \ No newline at end of file diff --git a/public/docs/_examples/tutorial/dart/lib/hero_detail_component.dart b/public/docs/_examples/tutorial/dart/lib/hero_detail_component.dart new file mode 100644 index 0000000000..6c4fe2ead6 --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/hero_detail_component.dart @@ -0,0 +1,30 @@ +import 'dart:html'; + +import 'package:angular2/core.dart'; +import 'package:angular2/router.dart'; + +import 'hero.dart'; +import 'hero_service.dart'; + +@Component( + selector: 'my-hero-detail', + templateUrl: 'hero_detail_component.html', + styleUrls: const ['hero_detail_component.css']) +class HeroDetailComponent implements OnInit { + @Input() Hero hero; + + final HeroService _heroService; + final RouteParams _routeParams; + + HeroDetailComponent(this._heroService, this._routeParams); + + ngOnInit() async { + int id = _routeParams.params['id'] as int; + hero ??= await _heroService.getHero(id); + } + + goBack() { + window.history.back(); + } +} + diff --git a/public/docs/_examples/tutorial/dart/lib/hero_detail_component.html b/public/docs/_examples/tutorial/dart/lib/hero_detail_component.html new file mode 100644 index 0000000000..6b36305f6c --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/hero_detail_component.html @@ -0,0 +1,10 @@ +
    +

    {{hero.name}} details!

    +
    + {{hero.id}}
    +
    + + +
    + +
    \ No newline at end of file diff --git a/public/docs/_examples/tutorial/dart/lib/hero_service.dart b/public/docs/_examples/tutorial/dart/lib/hero_service.dart new file mode 100644 index 0000000000..46a2463756 --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/hero_service.dart @@ -0,0 +1,21 @@ +import 'dart:async'; + +import 'package:angular2/angular2.dart'; + +import 'hero.dart'; +import 'mock_heroes.dart'; + +@Injectable() +class HeroService { + Future> getHeroes() => new Future(() => mockHeroes); + + Future> getHeroesSlowly() { + return new Future.delayed(const Duration(seconds: 2), () => mockHeroes); + } + + Future getHero(int id) async { + List heroes = await getHeroes(); + return heroes.firstWhere((h) => h.id == id); + } +} + diff --git a/public/docs/_examples/tutorial/dart/lib/heroes_component.css b/public/docs/_examples/tutorial/dart/lib/heroes_component.css new file mode 100644 index 0000000000..1e823bf9a4 --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/heroes_component.css @@ -0,0 +1,58 @@ +.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:hover { + color: #607D8B; + background-color: #EEE; + left: .1em; +} +.heroes li.selected:hover { + color: white; +} +.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; +} +button { + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} \ No newline at end of file diff --git a/public/docs/_examples/tutorial/dart/lib/heroes_component.dart b/public/docs/_examples/tutorial/dart/lib/heroes_component.dart new file mode 100644 index 0000000000..f98eb13e3a --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/heroes_component.dart @@ -0,0 +1,40 @@ +import 'package:angular2/core.dart'; +import 'package:angular2/router.dart'; + +import 'hero_service.dart'; +import 'hero_detail_component.dart'; +import 'hero.dart'; + +@Component( + selector: 'my-heroes', + templateUrl: 'heroes_component.html', + styleUrls: const ['heroes_component.css'], + directives: const [HeroDetailComponent]) +class HeroesComponent implements OnInit { + List heroes; + Hero selectedHero; + + final HeroService _heroService; + final Router _router; + + HeroesComponent(this._heroService, this._router); + + getHeroes() async { + heroes = await _heroService.getHeroes(); + } + + gotoDetail() { + _router.navigate([ + 'HeroDetail', + {'id': selectedHero.id} + ]); + } + + ngOnInit() { + getHeroes(); + } + + onSelect(Hero hero) { + selectedHero = hero; + } +} diff --git a/public/docs/_examples/tutorial/dart/lib/heroes_component.html b/public/docs/_examples/tutorial/dart/lib/heroes_component.html new file mode 100644 index 0000000000..36c6c953f4 --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/heroes_component.html @@ -0,0 +1,14 @@ +
    +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    +
    +

    {{selectedHero.name | uppercase}} is my hero

    + +
    +
    diff --git a/public/docs/_examples/tutorial/dart/lib/mock_heroes.dart b/public/docs/_examples/tutorial/dart/lib/mock_heroes.dart new file mode 100644 index 0000000000..55c28d3492 --- /dev/null +++ b/public/docs/_examples/tutorial/dart/lib/mock_heroes.dart @@ -0,0 +1,14 @@ +import 'hero.dart'; + +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") +]; diff --git a/public/docs/_examples/tutorial/dart/pubspec.yaml b/public/docs/_examples/tutorial/dart/pubspec.yaml new file mode 100644 index 0000000000..ddfafde27c --- /dev/null +++ b/public/docs/_examples/tutorial/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/tutorial/dart/web/index.html b/public/docs/_examples/tutorial/dart/web/index.html new file mode 100644 index 0000000000..d11d98d8d7 --- /dev/null +++ b/public/docs/_examples/tutorial/dart/web/index.html @@ -0,0 +1,13 @@ + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/tutorial/dart/web/main.dart b/public/docs/_examples/tutorial/dart/web/main.dart new file mode 100644 index 0000000000..dcc15e84f2 --- /dev/null +++ b/public/docs/_examples/tutorial/dart/web/main.dart @@ -0,0 +1,12 @@ +import 'package:angular2/bootstrap.dart'; +import 'package:angular2/router.dart'; + +import 'package:angular2_tour_of_heroes/hero_service.dart'; +import 'package:angular2_tour_of_heroes/app_component.dart'; + +main() { + bootstrap(AppComponent, [ + ROUTER_PROVIDERS, + HeroService + ]); +} diff --git a/public/docs/_examples/tutorial/dart/web/styles.css b/public/docs/_examples/tutorial/dart/web/styles.css new file mode 100644 index 0000000000..a624822892 --- /dev/null +++ b/public/docs/_examples/tutorial/dart/web/styles.css @@ -0,0 +1,5 @@ +h2 { color: #444; font-weight: lighter; } +body { margin: 2em; } +body, input[text], button { color: #888; font-family: Cambria, Georgia; } +button { padding: 0.2em; font-size: 14px} +* { font-family: Arial; } diff --git a/public/docs/dart/latest/_data.json b/public/docs/dart/latest/_data.json index 6cd1f36402..8c47f8c8bc 100644 --- a/public/docs/dart/latest/_data.json +++ b/public/docs/dart/latest/_data.json @@ -11,6 +11,12 @@ "title": "5 Min Quickstart" }, + "tutorial": { + "icon": "list", + "title": "Tutorial", + "banner": "Angular 2 is currently in Beta." + }, + "guide": { "icon": "list", "title": "Developers Guide", diff --git a/public/docs/dart/latest/tutorial/_data.json b/public/docs/dart/latest/tutorial/_data.json new file mode 100644 index 0000000000..d127c8f089 --- /dev/null +++ b/public/docs/dart/latest/tutorial/_data.json @@ -0,0 +1,24 @@ +{ + "_listtype": "ordered", + + "index": { + "title": "Tutorial: Tour of Heroes", + "intro": "The Tour of Heroes tutorial takes us through the steps of creating an Angular application in TypeScript." + }, + "toh-pt1": { + "title": "The Hero Editor", + "intro": "We build a simple hero editor" + }, + "toh-pt2": { + "title": "Master/Detail", + "intro": "We build a master/detail page with a list of heroes" + }, + "toh-pt3": { + "title": "Multiple Components", + "intro": "We refactor the master/detail view into separate components" + }, + "toh-pt4": { + "title": "Services", + "intro": "We create a reusable service to manage our hero data calls" + } +} \ No newline at end of file diff --git a/public/docs/dart/latest/tutorial/dependency-injection.1.jade b/public/docs/dart/latest/tutorial/dependency-injection.1.jade new file mode 100644 index 0000000000..c8fab3f81f --- /dev/null +++ b/public/docs/dart/latest/tutorial/dependency-injection.1.jade @@ -0,0 +1,623 @@ +include ../../../../_includes/_util-fns +:markdown + Dependency Injection is an important application design pattern. + Angular has its own Dependency Injection framework and + we really can't build an Angular application without it. + + In this chapter we'll learn what Dependency Injection is, why we want it, and how to use it. + +.l-main-section +:markdown + ## Why Dependency Injection? + + Let's start with the following code. + + ``` + class Engine {} + + class Tires {} + + class Car { + private engine: Engine; + private tires: Tires; + + constructor() { + this.engine = new Engine(); + this.tires = new Tires(); + } + // Method using the engine and tires + drive() {} + } + ``` + + Our `Car` creates everything it needs inside its constructor. + What's the problem? + + The problem is that our `Car` class is brittle, inflexible, and hard to test. + + Our `Car` needs an engine and tires. Instead of asking for them, + the `Car` constructor creates its own copies by "new-ing" them from + the very specific classes, `Engine` and `Tires`. + + What if the `Engine` class evolves and its constructor requires a parameter? + Our `Car` is broken and stays broken until we rewrite it along the lines of + `this.engine = new Engine(theNewParameter)`. + We didn't care about `Engine` constructor parameters when we first wrote `Car`. + We don't really care about them now. + But we'll *have* to start caring because + when the definion of `Engine` changes, our `Car` class must change. + That makes `Car` brittle. + + What if we want to put a different brand of tires on our `Car`. Too bad. + We're locked into whatever brand the `Tires` class creates. That makes our `Car` inflexible. + + Right now each new car gets its own engine. It can't share an engine with other cars. + While that makes sense for an automobile engine, + we can think of other dependencies that should be shared ... like the onboard + wireless connection to the manufacturer's service center. Our `Car` lacks the flexibility + to share services that have been created previously for other consumers. + + When we write tests for our `Car` we're at the mercy of its hidden dependencies. + Is it even possible to create a new `Engine` in a test environment? + What does `Engine`itself depend upon? What does that dependency depend on? + Will a new instance of `Engine` make an asynchronous call to the server? + We certainly don't want that going on during our tests. + + What if our `Car` should flash a warning signal when tire pressure is low. + How do we confirm that if actually does flash a warning + if we can't swap in low-pressure tires during the test? + + We have no control over the car's hidden dependencies. + When we can't control the dependencies, a class become difficult to test. + + How can we make `Car` more robust, more flexible, and more testable? + + That's super easy. We probably already know what to do. We change our `Car` constructor to this: + + ``` + constructor(engine: Engine, tires: Tires) { + this.engine = engine; + this.tires = tires; + } + ``` + See what happened? We moved the definition of the dependencies to the constructor. + Our `Car` class no longer creates an engine or tires. + It just consumes them. + + Now we create a car by passing the engine and tires to the constructor. + ``` + var car = new Car(new Engine(), new Tires()); + ``` + How cool is that? + The definition of the engine and tire dependencies are decoupled from the `Car` class itself. + We can pass in any kind of engine or tires we like, as long as they + conform to the general API requirements of an engine or tires. + + If someone extends the `Engine` class, that is not `Car`'s problem. +.l-sub-section + :markdown + The consumer of `Car` has the problem. The consumer must update the car creation code to + something like: + ``` + var car = new Car(new Engine(theNewParameter), new Tires()); + ``` + The critical point is this: `Car` itself did not have to change. + We'll take care of the consumer's problem soon enough. + +:markdown + The `Car` class is much easier to test because we are in complete control + of its dependencies. + We can pass mocks to the constructor that do exactly what we want them to do + during each test: + ``` + var car = new Car(new MockEngine(), new MockLowPressureTires()); + ``` + + **We just learned what Dependency Injection is**. + + It's a coding pattern in which a class receives its dependencies from external + sources rather than creating them itself. + + Cool! But what about that poor consumer? + Anyone who wants a `Car` must now + create all three parts: the `Car`, `Engine`, and `Tires`. + The `Car` class shed its problems at the consumer's expense. + We need something that takes care of assembling these parts for us. + + We could write a giant class to do that: + ``` + class SuperFactory { + createEngine = () => new Engine(); + createTires = () => new Tires(); + createCar = () => new Car(this.createEngine(), this.createTires()); + } + ``` + It's not so bad now with only three creation methods. + But maintaining it will be hairy as the application grows. + This `SuperFactory` is going to become a huge spider web of + interdependent factory methods! + + Wouldn't it be nice if we could simply list the things we want to build without + having to define which dependency gets injected into what? + + This is where the Dependency Injection Framework comes into play. + Imagine the framework had something called an `Injector`. + We register some classes with this `Injector` and it figures out how to create them. + + When we need a `Car`, we simply ask the `Injector` to get it for us and we're good to go. + ``` + function main() { + var injector = new Injector([Car, Engine, Tires, Logger]); + var car = injector.get(Car); + car.drive(); + } + ``` + Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`. + The consumer knows nothing about creating a `Car`. + We don't have a gigantic factory class to maintain. + Both `Car` and consumer simply ask for what they need and the `Injector` delivers. + + This is what a **Dependency InjectionFramework** is all about. + + Now that we know what Dependency Injection is and appreciate its benefits, + let's see how it is implemented in Angular. + +.l-main-section +:markdown + ## Angular Dependency Injection + + Angular ships with its own Dependency Injection framework. This framework can also be used + as a standalone module by other applications and frameworks. + + That sounds nice. What does it do for us when building components in Angular? + Let's see, one step at a time. + + We'll begin with a simplified version of the `HeroesComponent` + that we built in the [The Tour of Heroes](../tutorial/). + ``` + import {Component} from 'angular2/angular2'; + import {Hero} from './hero'; + import {HEROES} from './mock-heroes'; + + @Component({ + selector: 'my-heroes' + templateUrl: 'app/heroes.component.html' + }) + export class HeroesComponent { + + heroes: Hero[] = HEROES; + + } + ``` + It assigns a list of mocked heroes to its `heroes` property for binding within the template. + Pretty straight forward. + + Those heroes are currently a fixed, in-memory collection, defined in another file and imported by the component. + That works in the early stages of development but it's far from ideal. + As soon as we try to test this component or want to get our heroes data from a remote server, + we'll have to change this component's implementation of `heroes` and + fix every other use of the `HEROES` mock data. + + Let's make a service that hides how we get Hero data. +.l-sub-section + :markdown + Write this service in its own file. See [this note](#forward-ref) to understand why. +:markdown + ``` + import {Hero} from './hero'; + import {HEROES} from './mock-heroes'; + + class HeroService { + + heroes: Hero[]; + + constructor() { + this.heroes = HEROES; + } + + getHeroes() { + return this.heroes; + } + } + ``` + Our `HeroService` exposes a `getHeroes()` method that returns + the same mock data as before but none of its consumers need to know that. + + A service is nothing more than a class in Angular 2. + It remains nothing more than a class until we register it with + the Angular injector. + + ### Configuring the Injector + + We don't have to create the injector. + + Angular creates an application-wide injector for us during the bootstrap process. + ``` + bootstrap(HeroesComponent); + ``` + + Let’s configure the injector at the same time that we bootstrap by adding + our `HeroService` to an array in the second argument. + We'll explain that array when we talk about [providers](#providers) later in this chapter. + ``` + bootstrap(AppComponent, [HeroService]); + ``` + That’s it! The injector now knows about the `HeroService` which is available for injection across our entire application. + + ### Preparing the `HeroesComponent` for injection + + The `HeroesComponent` should get its heroes from this service. + Per the dependency injection pattern, the component must "ask for" the service in its constructor [as we explained + earlier](#ctor-injection)". + + ``` + constructor(heroService: HeroService) { + this.heroes = heroService.getHeroes(); + } + ``` + +.l-sub-section + :markdown + Adding a parameter to the constructor isn't all that's happening here. + + We are writing the app in TypeScript and have followed the parameter name with a type notation, `:HeroService`. + The class is also decorated with the `@Component` decorator (scroll up to confirm that fact). + + When the TypeScript compiler evaluates this class, it sees the decorator and adds class metadata + into the generated JavaScript code. Within that metadata lurks the information that + associates the `heroService` parameter with the `HeroService` class. + + That's how the Angular injector will know to inject an instance of the `HeroService` when it + creates a new `HeroesComponent`. +:markdown + ### Creating the `HeroesComponent` with the injector (implicitly) + When we introduced the idea of an injector above, we showed how to create + a new `Car` with that injector. + ``` + var car = injector.get(Car); + ``` + Search the entire Tour of Heroes source. We won't find a single line like + ``` + var hc = injector.get(HeroesComponent); + ``` + We *could* write code like that if we wanted to. We just don't have to. + Angular does that for us when it renders a `HeroesComponent` + whether we ask for it in an HTML template ... + ``` + + ``` + ... or navigate to a `HeroesComponent` view with the [router](./router.html). + + ### Singleton services + We might wonder what happens when we inject the `HeroService` into other components. + Do we get the same instance every time? + + Yes we do. Dependencies are singletons. + We’ll discuss that later in our chapter about + [Hierarchical Injectors](./hierarchical-dependency-injection.html). + + ### Testing the component + We emphasized earlier that designing a class for dependency injection makes it easier to test. + + Mission accomplished! We don't even need the Angular Dependency Injection system to test the `HeroesComponent`. + We simply create a bew `HeroesComponent` with a mock service and poke at it: + ``` + it("should have heroes when created", () => { + let hc = new HeroesComponent(mockService); + expect(hc.heroes.length).toEqual(mockService.getHeroes().length); + }) + ``` + ### When the service needs a service + Our `HeroService` is very simple. It doesn't have any dependencies of its own. + + + What if it had a dependency? What if it reported its activities through a logging service? + We'd apply the same "constructor injection" pattern. + + Here's a rewrite of `HeroService` with a new constructor that takes a `logger` parameter. + ``` + import {Hero} from './hero'; + import {HEROES} from './mock-heroes'; + import {Logger} from './logger'; + + @Injectable() + class HeroService { + + heroes: Hero[]; + + constructor(private logger: Logger) { + this.heroes = HEROES; + } + + getHeroes() { + this.logger.log('Getting heroes ...') + return this.heroes; + } + } + ``` + The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `logger`. + We call that property within our `getHeroes()` method when anyone asks for heroes. + + **The `@Injectable()` decoration catches our eye!** + +.alert.is-critical + :markdown + **Always include the parentheses!** Always call `@Injectable()`. It's easy to forget the parentheses. + Our application will fail mysteriously if we do. It bears repeating: **always include the parentheses.** +:markdown + We haven't seen `@Injectable()` before. + As it happens, we could have added it to `HeroService`. We didn't bother because we didn't need it then. + + We need it now ... now that our service has an injected dependency. + We need it because Angular requires constructor parameter metadata in order to inject a `Logger`. + As [we mentioned earlier](#di-metadata), TypeScript *only generates metadata for classes that have a decorator*. . + + The `HeroesComponent` has an injected dependency too. Why don't we add `@Injectable()` to the `HeroesComponent`? + We *can* add it if we really want to. It isn't necessary because the `HeroesComponent` is already decorated with `@Component`. + TypeScript generates metadata for *any* class with a decorator and *any* decorator will do. + +.l-main-section +:markdown + + ## Injector Providers + + Remember when we added the `HeroService` to an array in the [bootstrap](#bootstrap) process? + ``` + bootstrap(AppComponent, [HeroService]); + ``` + That list of classes is actually a list of **providers**. + + "Providers" create the instances of the things that we ask the injector to inject. + There are many ways ways to "provide" a thing that has the necessary shape and behavior to serve as a `HeroService`. + A class is a natural provider - it's meant to be created. But it's not the only way + to produce something injectable. We could hand the injector an object to return. We could give it a factory function to call. + Any of these approaches might be a good choice under the right circumstances. + + What matters is that the injector knows what to do when something asks for a `HeroService`. + + ### Provider mappings + When we registered the `HeroService` with the injector, we were actually registering + a mapping between the `HeroService` *token* and a provider that can create a `HeroService`. + + When we wrote ... + ``` + import {bootstrap} from 'angular2/angular2'; + + bootstrap(AppComponent, [HeroService]); + ``` + ... Angular translated that statement into a mapping instruction involving the Angular `provide` method + ``` + import {bootstrap, provide} from 'angular2/angular2'; + + bootstrap(AppComponent, [ + provide(HeroService, {useClass:HeroService}) + ]); + ``` + Of course we prefer the shorthand syntax - `[HeroService]` - when the provider and the token are the same class. + + Isn't that always the case? Not always. + + ### Alternative Class Providers + + Occasionally we'll ask a different class to provide the service. + + We do that regularly when testing a component that we're creating with dependency injection. + In this example, we tell the injector + to return a `MockHeroService` when something asks for the `HeroService`. + ``` + beforeEachProviders(() => [ + provide(HeroService, {useClass: MockHeroService}); + ]); + ``` + ### Value Providers + + Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class. + + We do that a lot when we write tests. We might write the following test setup + for tests that explore how the `HeroComponent` behaves when the `HeroService` + returns an empty hero list. + ``` + beforeEachProviders(() => { + + let emptyHeroService = { getHeroes: () => [] }; + + return [ provide(HeroService, {useValue: emptyHeroService}) ]; + }); + ``` + Notice that we mapped with `useValue` instead of `useClass`. + + ### Factory Providers + + Sometimes the best choice for a provider is neither a class nor a value. + + Suppose our HeroService has some cool new feature that we're only offering to "special" users. + The HeroService shouldn't know about users and + we won't know if the current user is special until runtime anyway. + We decide to extend our `HeroService` constructor to accept a `useCoolFeature` flag + that toggles the feature on or off. + We rewrite the `HeroService` again as follows. + ``` + @Injectable() + class HeroService { + + heroes: Hero[]; + + constructor(private logger: Logger, private useCoolFeature: boolean) { + this.heroes = HEROES; + } + + getHeroes() { + let msg = this.useCoolFeature ? 'the cool new way' : 'the old way'; + this.logger.log('Getting heroes ...' + msg) + return this.heroes; + } + } + ``` + The feature flag is a simple boolean value. We'd like to inject the flag but it seems silly to write an entire class for a + simple flag. + + We can replace the `HeroService` provider with a factory function that creates a properly configured `HeroService` for the current user. + We'll' build up to that result, beginning with our definition of the factory function: + ``` + let heroServiceFactory = (logger: Logger, userService: UserService) => { + return new HeroService(logger, userService.user.isSpecial); + } + ``` +.l-sub-section + :markdown + The factory takes two parameters: the logger service and a user service. + The logger we pass straight to the constructor as we did before. + + We'll know to use the cool new feature if the `userService.user.isSpecial` flag is true, + a fact we can't know until runtime. +:markdown + We use dependency injection everywhere so of course the factory function depends on + two injected services: `Logger` and `UserService`. + We declare those requirements in our provider definition object: + ``` + let heroServiceDefinition = { + useFactory: heroServiceFactory, + deps: [Logger, UserService] + }; + ``` +.l-sub-section + :markdown + The `useFactory` field tells Angular that the provider is a factory function and that its implementation is the `heroServiceFactory`. + + The `deps` property is an array of provider mapping tokens. + The `Logger` and `UserService` classes serve as tokens for their own class provider mappings. +:markdown + Finally, we create the mapping and adjust the bootstrapping to include that mapping in its provider configuration. + ``` + let heroServiceMapping = provide(HeroService, heroServiceDefinition); + + bootstrap(AppComponent, [heroServiceMapping, Logger, UserService]); + ``` + ### String tokens + + Sometimes we have an object dependency rather than a class dependency. + + Applications often define configuration objects with lots of small facts like the title of the application or the address of a web api endpoint. + These configuration objects aren't always instances of a class. They're just objects ... like this one: + ``` + let config = { + apiEndpoint: 'api.heroes.com', + title: 'The Hero Employment Agency' + }; + ``` + We'd like to make this `config` object available for injection. + We know we can register an object with a "Value Provider". But what do we use for the token? + + Until now, we've always had a class to use as the token for mapping. + The `HeroService` class was our token, whether we mapped it to another class, a value, or a factory provider. + This time we don't have a class. There is no `Config` class. + + Fortunately, a token can be either a JavaScript type (e.g. the class function) **or a string**. We'll map our configuration object + to a string! + ``` + bootstrap(AppComponent, [ + // other mappings // + provide('App.config', {useValue:config}) + ]); + ``` + Now let's update the `HeroesComponent` constructor so it can display the configured title. + Right now the constructor signature is + ``` + constructor(heroService: HeroService) + ``` + We might think we can write: + ``` + // FAIL! + constructor(heroService: HeroService, config: config) + ``` + That's not going to work. There is no type called `config` and we didn't register the `config` object under that name anyway. + We'll need a little help from another Angular decorator called `@Inject`. + ``` + import {Inject} from 'angular2/angulare2' + + constructor(heroService: HeroService, @Inject('app.config') config) + + ``` + +.l-main-section +:markdown + # Next Steps + We learned the basics of Angular Dependency Injection in this chapter. + + The Angular Dependency Injection is more capable than we've described. + We can learn more about its advanced features, beginning with its support for + a hierarchy of nested injectors in the next + [Dependency Injection chapter](./hierarchical-dependency-injection.html) + +.l-main-section + +:markdown + ### Appendix: Why we recommend one class per file + Developers expect one class per file. Multiple classes per file is confusing and is best avoided. + If we define every class in its own file, there is nothing in this note to worry about. + Move along! + + If we scorn this advice + and we add our `HeroService` class to the `HeroesComponent` file anyway, + **define the `HeroesComponent` last!** + If we put it define component before the service, + we'll get a runtime null reference error. + + To understand why, paste the following incorrect, ultra-simplified rendition of these two + classes into the [TypeScript playground](http://www.typescriptlang.org/Playground). + + ``` + class HeroesComponent { + static $providers=[HeroService] + } + + class HeroService { } + + alert(HeroesComponent.$providers) + ``` +.l-sub-section + :markdown + The `HeroService` is incorrectly defined below the `HeroComponent`. + + The `$providers` static property represents the metadata about the injected `HeroService` + that TypeScript compiler would add to the component class. + + The `alert` simulates the action of the Dependency Injector at runtime + when it attempts to create a `HeroesComponent`. +:markdown + Run it. The alert appears but displays nothing. + This is the equivalent of the null reference error thrown at runtime. + + We understand why when we review the generated JavaScript which looks like this: + ``` + var HeroesComponent = (function () { + function HeroesComponent() { + } + HeroesComponent.$providers = [HeroService]; + return HeroesComponent; + })(); + + var HeroService = (function () { + function HeroService() { + } + return HeroService; + })(); + + alert(HeroesComponent.$providers); + ``` + + Notice that the TypeScript compiler turns classes into function expressions + assigned to variables. The value of the captured `HeroService` variable is undefined + when the `$providers` array is assigned. The `HeroService` variable gets its value too late + to be captured. + + Reverse the order of class definition so that the `HeroService` + appears before the `HeroesComponent` that requires it. + Run again. This time the alert displays the `HeroService` function definition. + + If we insist on defining the `HeroService` in the same file and insist on + defining the component first, Angular offers a way to make that work. + The `forwardRef()` method let's us reference a class + before it has been defined. + Learn more about this problem and the `forwardRef()` + in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html). diff --git a/public/docs/dart/latest/tutorial/dependency-injection.jade b/public/docs/dart/latest/tutorial/dependency-injection.jade new file mode 100644 index 0000000000..d2be8029e3 --- /dev/null +++ b/public/docs/dart/latest/tutorial/dependency-injection.jade @@ -0,0 +1,623 @@ +include ../../../../_includes/_util-fns +:markdown + Dependency Injection is an important application design pattern. + Angular has its own Dependency Injection framework and + we really can't build an Angular application without it. + + In this chapter we'll learn what Dependency Injection is, why we want it, and how to use it. + +.l-main-section +:markdown + ## Why Dependency Injection? + + Let's start with the following code. + + ``` + class Engine {} + + class Tires {} + + class Car { + private engine: Engine; + private tires: Tires; + + constructor() { + this.engine = new Engine(); + this.tires = new Tires(); + } + // Method using the engine and tires + drive() {} + } + ``` + + Our `Car` creates everything it needs inside its constructor. + What's the problem? + + The problem is that our `Car` class is brittle, inflexible, and hard to test. + + Our `Car` needs an engine and tires. Instead of asking for them, + the `Car` constructor creates its own copies by "new-ing" them from + the very specific classes, `Engine` and `Tires`. + + What if the `Engine` class evolves and its constructor requires a parameter? + Our `Car` is broken and stays broken until we rewrite it along the lines of + `this.engine = new Engine(theNewParameter)`. + We didn't care about `Engine` constructor parameters when we first wrote `Car`. + We don't really care about them now. + But we'll *have* to start caring because + when the definion of `Engine` changes, our `Car` class must change. + That makes `Car` brittle. + + What if we want to put a different brand of tires on our `Car`. Too bad. + We're locked into whatever brand the `Tires` class creates. That makes our `Car` inflexible. + + Right now each new car gets its own engine. It can't share an engine with other cars. + While that makes sense for an automobile engine, + we can think of other dependencies that should be shared ... like the onboard + wireless connection to the manufacturer's service center. Our `Car` lacks the flexibility + to share services that have been created previously for other consumers. + + When we write tests for our `Car` we're at the mercy of its hidden dependencies. + Is it even possible to create a new `Engine` in a test environment? + What does `Engine`itself depend upon? What does that dependency depend on? + Will a new instance of `Engine` make an asynchronous call to the server? + We certainly don't want that going on during our tests. + + What if our `Car` should flash a warning signal when tire pressure is low. + How do we confirm that if actually does flash a warning + if we can't swap in low-pressure tires during the test? + + We have no control over the car's hidden dependencies. + When we can't control the dependencies, a class become difficult to test. + + How can we make `Car` more robust, more flexible, and more testable? + + That's super easy. We probably already know what to do. We change our `Car` constructor to this: + + ``` + constructor(engine: Engine, tires: Tires) { + this.engine = engine; + this.tires = tires; + } + ``` + See what happened? We moved the definition of the dependencies to the constructor. + Our `Car` class no longer creates an engine or tires. + It just consumes them. + + Now we create a car by passing the engine and tires to the constructor. + ``` + var car = new Car(new Engine(), new Tires()); + ``` + How cool is that? + The definition of the engine and tire dependencies are decoupled from the `Car` class itself. + We can pass in any kind of engine or tires we like, as long as they + conform to the general API requirements of an engine or tires. + + If someone extends the `Engine` class, that is not `Car`'s problem. +.l-sub-section + :markdown + The consumer of `Car` has the problem. The consumer must update the car creation code to + something like: + ``` + var car = new Car(new Engine(theNewParameter), new Tires()); + ``` + The critical point is this: `Car` itself did not have to change. + We'll take care of the consumer's problem soon enough. + +:markdown + The `Car` class is much easier to test because we are in complete control + of its dependencies. + We can pass mocks to the constructor that do exactly what we want them to do + during each test: + ``` + var car = new Car(new MockEngine(), new MockLowPressureTires()); + ``` + + **We just learned what Dependency Injection is**. + + It's a coding pattern in which a class receives its dependencies from external + sources rather than creating them itself. + + Cool! But what about that poor consumer? + Anyone who wants a `Car` must now + create all three parts: the `Car`, `Engine`, and `Tires`. + The `Car` class shed its problems at the consumer's expense. + We need something that takes care of assembling these parts for us. + + We could write a giant class to do that: + ``` + class SuperFactory { + createEngine = () => new Engine(); + createTires = () => new Tires(); + createCar = () => new Car(this.createEngine(), this.createTires()); + } + ``` + It's not so bad now with only three creation methods. + But maintaining it will be hairy as the application grows. + This `SuperFactory` is going to become a huge spider web of + interdependent factory methods! + + Wouldn't it be nice if we could simply list the things we want to build without + having to define which dependency gets injected into what? + + This is where the Dependency Injection Framework comes into play. + Imagine the framework had something called an `Injector`. + We register some classes with this `Injector` and it figures out how to create them. + + When we need a `Car`, we simply ask the `Injector` to get it for us and we're good to go. + ``` + function main() { + var injector = new Injector([Car, Engine, Tires, Logger]); + var car = injector.get(Car); + car.drive(); + } + ``` + Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`. + The consumer knows nothing about creating a `Car`. + We don't have a gigantic factory class to maintain. + Both `Car` and consumer simply ask for what they need and the `Injector` delivers. + + This is what a **Dependency Injection Framework** is all about. + + Now that we know what Dependency Injection is and appreciate its benefits, + let's see how it is implemented in Angular. + +.l-main-section +:markdown + ## Angular Dependency Injection + + Angular ships with its own Dependency Injection framework. This framework can also be used + as a standalone module by other applications and frameworks. + + That sounds nice. What does it do for us when building components in Angular? + Let's see, one step at a time. + + We'll begin with a simplified version of the `HeroesComponent` + that we built in the [The Tour of Heroes](../tutorial/). + ``` + import {Component} from 'angular2/core'; + import {Hero} from './hero'; + import {HEROES} from './mock-heroes'; + + @Component({ + selector: 'my-heroes' + templateUrl: 'app/heroes.component.html' + }) + export class HeroesComponent { + + heroes: Hero[] = HEROES; + + } + ``` + It assigns a list of mocked heroes to its `heroes` property for binding within the template. + Pretty straight forward. + + Those heroes are currently a fixed, in-memory collection, defined in another file and imported by the component. + That works in the early stages of development but it's far from ideal. + As soon as we try to test this component or want to get our heroes data from a remote server, + we'll have to change this component's implementation of `heroes` and + fix every other use of the `HEROES` mock data. + + Let's make a service that hides how we get Hero data. +.l-sub-section + :markdown + Write this service in its own file. See [this note](#forward-ref) to understand why. +:markdown + ``` + import {Hero} from './hero'; + import {HEROES} from './mock-heroes'; + + class HeroService { + + heroes: Hero[]; + + constructor() { + this.heroes = HEROES; + } + + getHeroes() { + return this.heroes; + } + } + ``` + Our `HeroService` exposes a `getHeroes()` method that returns + the same mock data as before but none of its consumers need to know that. + + A service is nothing more than a class in Angular 2. + It remains nothing more than a class until we register it with + the Angular injector. + + ### Configuring the Injector + + We don't have to create the injector. + + Angular creates an application-wide injector for us during the bootstrap process. + ``` + bootstrap(HeroesComponent); + ``` + + Let’s configure the injector at the same time that we bootstrap by adding + our `HeroService` to an array in the second argument. + We'll explain that array when we talk about [providers](#providers) later in this chapter. + ``` + bootstrap(AppComponent, [HeroService]); + ``` + That’s it! The injector now knows about the `HeroService` which is available for injection across our entire application. + + ### Preparing the `HeroesComponent` for injection + + The `HeroesComponent` should get its heroes from this service. + Per the dependency injection pattern, the component must "ask for" the service in its constructor [as we explained + earlier](#ctor-injection)". + + ``` + constructor(heroService: HeroService) { + this.heroes = heroService.getHeroes(); + } + ``` + +.l-sub-section + :markdown + Adding a parameter to the constructor isn't all that's happening here. + + We are writing the app in TypeScript and have followed the parameter name with a type notation, `:HeroService`. + The class is also decorated with the `@Component` decorator (scroll up to confirm that fact). + + When the TypeScript compiler evaluates this class, it sees the decorator and adds class metadata + into the generated JavaScript code. Within that metadata lurks the information that + associates the `heroService` parameter with the `HeroService` class. + + That's how the Angular injector will know to inject an instance of the `HeroService` when it + creates a new `HeroesComponent`. +:markdown + ### Creating the `HeroesComponent` with the injector (implicitly) + When we introduced the idea of an injector above, we showed how to create + a new `Car` with that injector. + ``` + var car = injector.get(Car); + ``` + Search the entire Tour of Heroes source. We won't find a single line like + ``` + var hc = injector.get(HeroesComponent); + ``` + We *could* write code like that if we wanted to. We just don't have to. + Angular does that for us when it renders a `HeroesComponent` + whether we ask for it in an HTML template ... + ``` + + ``` + ... or navigate to a `HeroesComponent` view with the [router](./router.html). + + ### Singleton services + We might wonder what happens when we inject the `HeroService` into other components. + Do we get the same instance every time? + + Yes we do. Dependencies are singletons. + We’ll discuss that later in our chapter about + [Hierarchical Injectors](./hierarchical-dependency-injection.html). + + ### Testing the component + We emphasized earlier that designing a class for dependency injection makes it easier to test. + + Mission accomplished! We don't even need the Angular Dependency Injection system to test the `HeroesComponent`. + We simply create a bew `HeroesComponent` with a mock service and poke at it: + ``` + it("should have heroes when created", () => { + let hc = new HeroesComponent(mockService); + expect(hc.heroes.length).toEqual(mockService.getHeroes().length); + }) + ``` + ### When the service needs a service + Our `HeroService` is very simple. It doesn't have any dependencies of its own. + + + What if it had a dependency? What if it reported its activities through a logging service? + We'd apply the same "constructor injection" pattern. + + Here's a rewrite of `HeroService` with a new constructor that takes a `logger` parameter. + ``` + import {Hero} from './hero'; + import {HEROES} from './mock-heroes'; + import {Logger} from './logger'; + + @Injectable() + class HeroService { + + heroes: Hero[]; + + constructor(private logger: Logger) { + this.heroes = HEROES; + } + + getHeroes() { + this.logger.log('Getting heroes ...') + return this.heroes; + } + } + ``` + The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `logger`. + We call that property within our `getHeroes()` method when anyone asks for heroes. + + **The `@Injectable()` decoration catches our eye!** + +.alert.is-critical + :markdown + **Always include the parentheses!** Always call `@Injectable()`. It's easy to forget the parentheses. + Our application will fail mysteriously if we do. It bears repeating: **always include the parentheses.** +:markdown + We haven't seen `@Injectable()` before. + As it happens, we could have added it to `HeroService`. We didn't bother because we didn't need it then. + + We need it now ... now that our service has an injected dependency. + We need it because Angular requires constructor parameter metadata in order to inject a `Logger`. + As [we mentioned earlier](#di-metadata), TypeScript *only generates metadata for classes that have a decorator*. . + + The `HeroesComponent` has an injected dependency too. Why don't we add `@Injectable()` to the `HeroesComponent`? + We *can* add it if we really want to. It isn't necessary because the `HeroesComponent` is already decorated with `@Component`. + TypeScript generates metadata for *any* class with a decorator and *any* decorator will do. + +.l-main-section +:markdown + + ## Injector Providers + + Remember when we added the `HeroService` to an array in the [bootstrap](#bootstrap) process? + ``` + bootstrap(AppComponent, [HeroService]); + ``` + That list of classes is actually a list of **providers**. + + "Providers" create the instances of the things that we ask the injector to inject. + There are many ways ways to "provide" a thing that has the necessary shape and behavior to serve as a `HeroService`. + A class is a natural provider - it's meant to be created. But it's not the only way + to produce something injectable. We could hand the injector an object to return. We could give it a factory function to call. + Any of these approaches might be a good choice under the right circumstances. + + What matters is that the injector knows what to do when something asks for a `HeroService`. + + ### Provider mappings + When we registered the `HeroService` with the injector, we were actually registering + a mapping between the `HeroService` *token* and a provider that can create a `HeroService`. + + When we wrote ... + ``` + import {bootstrap} from 'angular2/platform/browser'; + + bootstrap(AppComponent, [HeroService]); + ``` + ... Angular translated that statement into a mapping instruction involving the Angular `provide` method + ``` + import {bootstrap, provide} from 'angular2/platform/browser'; + + bootstrap(AppComponent, [ + provide(HeroService, {useClass:HeroService}) + ]); + ``` + Of course we prefer the shorthand syntax - `[HeroService]` - when the provider and the token are the same class. + + Isn't that always the case? Not always. + + ### Alternative Class Providers + + Occasionally we'll ask a different class to provide the service. + + We do that regularly when testing a component that we're creating with dependency injection. + In this example, we tell the injector + to return a `MockHeroService` when something asks for the `HeroService`. + ``` + beforeEachProviders(() => [ + provide(HeroService, {useClass: MockHeroService}); + ]); + ``` + ### Value Providers + + Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class. + + We do that a lot when we write tests. We might write the following test setup + for tests that explore how the `HeroComponent` behaves when the `HeroService` + returns an empty hero list. + ``` + beforeEachProviders(() => { + + let emptyHeroService = { getHeroes: () => [] }; + + return [ provide(HeroService, {useValue: emptyHeroService}) ]; + }); + ``` + Notice that we mapped with `useValue` instead of `useClass`. + + ### Factory Providers + + Sometimes the best choice for a provider is neither a class nor a value. + + Suppose our HeroService has some cool new feature that we're only offering to "special" users. + The HeroService shouldn't know about users and + we won't know if the current user is special until runtime anyway. + We decide to extend our `HeroService` constructor to accept a `useCoolFeature` flag + that toggles the feature on or off. + We rewrite the `HeroService` again as follows. + ``` + @Injectable() + class HeroService { + + heroes: Hero[]; + + constructor(private logger: Logger, private useCoolFeature: boolean) { + this.heroes = HEROES; + } + + getHeroes() { + let msg = this.useCoolFeature ? 'the cool new way' : 'the old way'; + this.logger.log('Getting heroes ...' + msg) + return this.heroes; + } + } + ``` + The feature flag is a simple boolean value. We'd like to inject the flag but it seems silly to write an entire class for a + simple flag. + + We can replace the `HeroService` provider with a factory function that creates a properly configured `HeroService` for the current user. + We'll' build up to that result, beginning with our definition of the factory function: + ``` + let heroServiceFactory = (logger: Logger, userService: UserService) => { + return new HeroService(logger, userService.user.isSpecial); + } + ``` +.l-sub-section + :markdown + The factory takes two parameters: the logger service and a user service. + The logger we pass straight to the constructor as we did before. + + We'll know to use the cool new feature if the `userService.user.isSpecial` flag is true, + a fact we can't know until runtime. +:markdown + We use dependency injection everywhere so of course the factory function depends on + two injected services: `Logger` and `UserService`. + We declare those requirements in our provider definition object: + ``` + let heroServiceDefinition = { + useFactory: heroServiceFactory, + deps: [Logger, UserService] + }; + ``` +.l-sub-section + :markdown + The `useFactory` field tells Angular that the provider is a factory function and that its implementation is the `heroServiceFactory`. + + The `deps` property is an array of provider mapping tokens. + The `Logger` and `UserService` classes serve as tokens for their own class provider mappings. +:markdown + Finally, we create the mapping and adjust the bootstrapping to include that mapping in its provider configuration. + ``` + let heroServiceMapping = provide(HeroService, heroServiceDefinition); + + bootstrap(AppComponent, [heroServiceMapping, Logger, UserService]); + ``` + ### String tokens + + Sometimes we have an object dependency rather than a class dependency. + + Applications often define configuration objects with lots of small facts like the title of the application or the address of a web api endpoint. + These configuration objects aren't always instances of a class. They're just objects ... like this one: + ``` + let config = { + apiEndpoint: 'api.heroes.com', + title: 'The Hero Employment Agency' + }; + ``` + We'd like to make this `config` object available for injection. + We know we can register an object with a "Value Provider". But what do we use for the token? + + Until now, we've always had a class to use as the token for mapping. + The `HeroService` class was our token, whether we mapped it to another class, a value, or a factory provider. + This time we don't have a class. There is no `Config` class. + + Fortunately, a token can be either a JavaScript type (e.g. the class function) **or a string**. We'll map our configuration object + to a string! + ``` + bootstrap(AppComponent, [ + // other mappings // + provide('App.config', {useValue:config}) + ]); + ``` + Now let's update the `HeroesComponent` constructor so it can display the configured title. + Right now the constructor signature is + ``` + constructor(heroService: HeroService) + ``` + We might think we can write: + ``` + // FAIL! + constructor(heroService: HeroService, config: config) + ``` + That's not going to work. There is no type called `config` and we didn't register the `config` object under that name anyway. + We'll need a little help from another Angular decorator called `@Inject`. + ``` + import {Inject} from 'angular2/core' + + constructor(heroService: HeroService, @Inject('app.config') config) + + ``` + +.l-main-section +:markdown + # Next Steps + We learned the basics of Angular Dependency Injection in this chapter. + + The Angular Dependency Injection is more capable than we've described. + We can learn more about its advanced features, beginning with its support for + a hierarchy of nested injectors in the next + [Dependency Injection chapter](./hierarchical-dependency-injection.html) + +.l-main-section + +:markdown + ### Appendix: Why we recommend one class per file + Developers expect one class per file. Multiple classes per file is confusing and is best avoided. + If we define every class in its own file, there is nothing in this note to worry about. + Move along! + + If we scorn this advice + and we add our `HeroService` class to the `HeroesComponent` file anyway, + **define the `HeroesComponent` last!** + If we put it define component before the service, + we'll get a runtime null reference error. + + To understand why, paste the following incorrect, ultra-simplified rendition of these two + classes into the [TypeScript playground](http://www.typescriptlang.org/Playground). + + ``` + class HeroesComponent { + static $providers=[HeroService] + } + + class HeroService { } + + alert(HeroesComponent.$providers) + ``` +.l-sub-section + :markdown + The `HeroService` is incorrectly defined below the `HeroComponent`. + + The `$providers` static property represents the metadata about the injected `HeroService` + that TypeScript compiler would add to the component class. + + The `alert` simulates the action of the Dependency Injector at runtime + when it attempts to create a `HeroesComponent`. +:markdown + Run it. The alert appears but displays nothing. + This is the equivalent of the null reference error thrown at runtime. + + We understand why when we review the generated JavaScript which looks like this: + ``` + var HeroesComponent = (function () { + function HeroesComponent() { + } + HeroesComponent.$providers = [HeroService]; + return HeroesComponent; + })(); + + var HeroService = (function () { + function HeroService() { + } + return HeroService; + })(); + + alert(HeroesComponent.$providers); + ``` + + Notice that the TypeScript compiler turns classes into function expressions + assigned to variables. The value of the captured `HeroService` variable is undefined + when the `$providers` array is assigned. The `HeroService` variable gets its value too late + to be captured. + + Reverse the order of class definition so that the `HeroService` + appears before the `HeroesComponent` that requires it. + Run again. This time the alert displays the `HeroService` function definition. + + If we insist on defining the `HeroService` in the same file and insist on + defining the component first, Angular offers a way to make that work. + The `forwardRef()` method let's us reference a class + before it has been defined. + Learn more about this problem and the `forwardRef()` + in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html). diff --git a/public/docs/dart/latest/tutorial/index.jade b/public/docs/dart/latest/tutorial/index.jade new file mode 100644 index 0000000000..a02c229dec --- /dev/null +++ b/public/docs/dart/latest/tutorial/index.jade @@ -0,0 +1,87 @@ +include ../../../../_includes/_util-fns + +:marked + # Tour of Heroes: the vision + + Our grand plan is to build an app to help a staffing agency manage its stable of heroes. + Even heroes need to find work. + + Of course we'll only make a little progress in this tutorial. What we do build will + have many of the features we expect to find in a full-blown, data-driven application: acquiring and displaying + a list of heroes, editing a selected hero's detail, and navigating among different + views of heroic data. + + The Tour of Heroes covers the core fundamentals of Angular. + We’ll use built-in directives to show/hide elements and display lists of hero data. + We’ll create a component to display hero details and another to show an array of heroes. + We'll use one-way data binding for read-only data. We'll add editable fields to update a model + with two-way data binding. We'll bind component method to user events like key strokes and clicks. + We’ll learn to select a hero from a master list and edit that hero in the details view. We'll + format data with pipes. We'll create a shared service to assemble our heroes. And we'll use routing to navigate among different views and their components. + + We’ll learn enough core Angular to get started and gain confidence that + Angular can do whatever we need it to do. + We'll be covering a lot of ground at an introductory level but we’ll find plenty of links + to chapters with greater depth. + + + +.l-main-section +:marked + ## The End Game + + Here's a visual idea of where we're going in this tour, beginning with the "Dashboard" + view and our most heroic heroes: + +figure.image-display + img(src='/resources/images/devguide/toh/heroes-dashboard-1.png' alt="Output of heroes dashboard") + +:marked + Above the dashboard are two links ("Dashboard" and "Heroes"). + We could click them to navigate between this Dashboard and a Heroes view. + + Instead we click the dashboard hero named "Magneta" and the router takes us to a "Hero Details" view + of that hero where we can change the hero's name. + +figure.image-display + img(src='/resources/images/devguide/toh/hero-details-1.png' alt="Details of hero in app") + +:marked + Links at the top can take us to either of the main views. + We'll click the "Back" button which sends us to the "Heroes" master list view with + "Magneta" as the selected hero. + +figure.image-display + img(src='/resources/images/devguide/toh/heroes-list-2.png' alt="Output of heroes list app") + +:marked + We click a different hero and the readonly mini-detail beneath the list reflects our new choice. + + We click the "View Details" button to drill into the + editable details of our selected hero. + + The following diagram captures all of our navigation options. + +figure.image-display + img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations") + +:marked + Here's our app in action + +figure.image-display + img(src='/resources/images/devguide/toh/toh-anim.gif' alt="Tour of Heroes in Action") + +.l-main-section +:marked + ## Up Next + + We’ll build this Tour of Heroes together, step by step. + We'll motivate each step with a requirement that we've + met in countless applications. Everything has a reason. + + And we’ll meet many of the core fundamentals of Angular along the way. + + [Let's get started!](./toh-pt1.html) diff --git a/public/docs/dart/latest/tutorial/toh-pt1.jade b/public/docs/dart/latest/tutorial/toh-pt1.jade new file mode 100644 index 0000000000..17fc597b61 --- /dev/null +++ b/public/docs/dart/latest/tutorial/toh-pt1.jade @@ -0,0 +1,191 @@ +include ../../../../_includes/_util-fns + +:marked + # Once Upon a Time + + Every story starts somewhere. Our story starts where the [QuickStart](../quickstart.html) ends. + + + + Follow the "QuickStart" steps. They provide the prerequisites, the folder structure, + and the core files for our Tour of Heroes. + + + + Copy the "QuickStart" code to a new folder and rename the folder `angular2_tour_of_heroes`. + We should have the following structure: + +.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 command runs the compiler in watch mode, starts the server, + and keeps the app running while we continue to build the Tour of Heroes. + +.l-main-section + :marked + ## Show our Hero + We want to display Hero data in our app + + Let's add two properties to our `AppComponent`, a `title` property for the application name and a `hero` property + for a hero named "Windstorm". + + +makeExample('toh-1/dart-snippets/app_component_snippets_pt1.dart', 'app-component-1', 'app_component.dart (AppComponent class)')(format=".") + + :marked + Now we update the template in the `@Component` decoration with data bindings to these new properties. + + +makeExample('toh-1/dart-snippets/app_component_snippets_pt1.dart', 'show-hero') + + :marked + The browser should refresh and display our title and hero. + + The double curly braces tell our app to read the `title` and `hero` properties from the component and render them. + This is the "interpolation" form of one-way data binding. +.l-sub-section + :marked + Learn more about interpolation in the [Displaying Data chapter](../guide/displaying-data.html). +:marked + ### Hero object + + At the moment, our hero is just a name. Our hero needs more properties. + Let's convert the `hero` from a literal string to a class. + + Create a `Hero` class with `id` and `name` properties. + Keep this near the top of the `app_component.dart` file for now. + ++makeExample('toh-1/dart/lib/app_component.dart', 'hero-class-1', 'app_component.dart (Hero class)')(format=".") + +:marked + Now that we have a `Hero` class, let’s refactor our component’s `hero` property to be of type `Hero`. + Then initialize it with an id of `1` and the name, "Windstorm". + ++makeExample('toh-1/dart-snippets/app_component_snippets_pt1.dart', 'hero-property-1', 'app_component.dart (Hero property)')(format=".") + +:marked + Because we changed the hero from a string to an object, + we update the binding in the template to refer to the hero’s `name` property. + ++makeExample('toh-1/dart-snippets/app_component_snippets_pt1.dart', 'show-hero-2') +:marked + The browser refreshes and continues to display our hero’s name. + + ### Adding more HTML + Displaying a name is good, but we want to see all of our hero’s properties. + We’ll add a `
    ` for our hero’s `id` property and another `
    ` for our hero’s `name`. + ++makeExample('toh-1/dart-snippets/app_component_snippets_pt1.dart', 'show-hero-properties') +:marked + Uh oh, our template string is getting long. We better take care of that to avoid the risk of making a typo in the template. + + ### Multi-line template strings + + We could make a more readable template with string concatenation + but that gets ugly fast, it is harder to read, and + it is easy to make a spelling error. Instead, + let’s take advantage of the template strings feature + in Dart to maintain our sanity. + + Change the quotes around the template to triple quotes and + put the `

    `, `

    ` and `
    ` elements on their own lines. + ++makeExample('toh-1/dart-snippets/app_component_snippets_pt1.dart', 'multi-line-strings', 'app_component.dart (AppComponent\'s template)') + + .callout.is-important + header A back-tick is not a single quote + :marked + **Be careful!** A back-tick (`) looks a lot like a single quote ('). + It's actually a completely different character. + Back-ticks can do more than demarcate a string. + Here we use them in a limited way to spread the template over multiple lines. + Everything between the back-ticks at the beginning and end of the template + is part of a single template string. + +.l-main-section +:marked + ## Editing Our Hero + + We want to be able to edit the hero name in a textbox. + + Refactor the hero name `