From e4db340464f5955670c0ce4e61587875a7d334b3 Mon Sep 17 00:00:00 2001 From: Torgeir Helgevold Date: Sat, 9 Apr 2016 00:18:37 -0400 Subject: [PATCH] docs:(TOH chapter on Http) New Tour of Hero Chapter on Http --- .../_examples/toh-4/ts/app/app.component.ts | 4 +- .../_examples/toh-5/ts/app/app.component.ts | 2 +- public/docs/_examples/toh-6/e2e-spec.js | 133 +++++++ public/docs/_examples/toh-6/ts/.gitignore | 1 + .../_examples/toh-6/ts/app/app.component.css | 31 ++ .../_examples/toh-6/ts/app/app.component.ts | 36 ++ .../toh-6/ts/app/dashboard.component.css | 63 ++++ .../toh-6/ts/app/dashboard.component.html | 11 + .../toh-6/ts/app/dashboard.component.ts | 32 ++ .../toh-6/ts/app/hero-detail.component.css | 30 ++ .../toh-6/ts/app/hero-detail.component.html | 13 + .../toh-6/ts/app/hero-detail.component.ts | 53 +++ .../_examples/toh-6/ts/app/hero.service.ts | 99 +++++ public/docs/_examples/toh-6/ts/app/hero.ts | 4 + .../toh-6/ts/app/heroes.component.css | 59 +++ .../toh-6/ts/app/heroes.component.html | 21 ++ .../toh-6/ts/app/heroes.component.ts | 66 ++++ .../toh-6/ts/app/in-memory-data.service.ts | 12 + public/docs/_examples/toh-6/ts/app/main.ts | 29 ++ .../_examples/toh-6/ts/example-config.json | 0 public/docs/_examples/toh-6/ts/index.html | 27 ++ public/docs/_examples/toh-6/ts/plnkr.json | 9 + public/docs/_examples/toh-6/ts/sample.css | 8 + public/docs/ts/latest/tutorial/_data.json | 5 + public/docs/ts/latest/tutorial/toh-pt5.jade | 1 + public/docs/ts/latest/tutorial/toh-pt6.jade | 350 ++++++++++++++++++ .../devguide/toh/hero-details-save-button.png | Bin 0 -> 17969 bytes .../toh/heroes-list-delete-button.png | Bin 0 -> 17009 bytes 28 files changed, 1096 insertions(+), 3 deletions(-) create mode 100644 public/docs/_examples/toh-6/e2e-spec.js create mode 100644 public/docs/_examples/toh-6/ts/.gitignore create mode 100644 public/docs/_examples/toh-6/ts/app/app.component.css create mode 100644 public/docs/_examples/toh-6/ts/app/app.component.ts create mode 100644 public/docs/_examples/toh-6/ts/app/dashboard.component.css create mode 100644 public/docs/_examples/toh-6/ts/app/dashboard.component.html create mode 100644 public/docs/_examples/toh-6/ts/app/dashboard.component.ts create mode 100644 public/docs/_examples/toh-6/ts/app/hero-detail.component.css create mode 100644 public/docs/_examples/toh-6/ts/app/hero-detail.component.html create mode 100644 public/docs/_examples/toh-6/ts/app/hero-detail.component.ts create mode 100644 public/docs/_examples/toh-6/ts/app/hero.service.ts create mode 100644 public/docs/_examples/toh-6/ts/app/hero.ts create mode 100644 public/docs/_examples/toh-6/ts/app/heroes.component.css create mode 100644 public/docs/_examples/toh-6/ts/app/heroes.component.html create mode 100644 public/docs/_examples/toh-6/ts/app/heroes.component.ts create mode 100644 public/docs/_examples/toh-6/ts/app/in-memory-data.service.ts create mode 100644 public/docs/_examples/toh-6/ts/app/main.ts create mode 100644 public/docs/_examples/toh-6/ts/example-config.json create mode 100644 public/docs/_examples/toh-6/ts/index.html create mode 100644 public/docs/_examples/toh-6/ts/plnkr.json create mode 100644 public/docs/_examples/toh-6/ts/sample.css create mode 100644 public/docs/ts/latest/tutorial/toh-pt6.jade create mode 100644 public/resources/images/devguide/toh/hero-details-save-button.png create mode 100644 public/resources/images/devguide/toh/heroes-list-delete-button.png diff --git a/public/docs/_examples/toh-4/ts/app/app.component.ts b/public/docs/_examples/toh-4/ts/app/app.component.ts index 9b0de3caeb..7ca63ee237 100644 --- a/public/docs/_examples/toh-4/ts/app/app.component.ts +++ b/public/docs/_examples/toh-4/ts/app/app.component.ts @@ -23,8 +23,8 @@ import { HeroService } from './hero.service'; `, - // #enddocregion template - styles:[` + // #enddocregion template + styles: [` .selected { background-color: #CFD8DC !important; color: white; diff --git a/public/docs/_examples/toh-5/ts/app/app.component.ts b/public/docs/_examples/toh-5/ts/app/app.component.ts index bedaca3e38..8d26ea6d90 100644 --- a/public/docs/_examples/toh-5/ts/app/app.component.ts +++ b/public/docs/_examples/toh-5/ts/app/app.component.ts @@ -3,12 +3,12 @@ import { Component } from '@angular/core'; import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '@angular/router-deprecated'; -import { HeroService } from './hero.service'; import { DashboardComponent } from './dashboard.component'; import { HeroesComponent } from './heroes.component'; // #docregion hero-detail-import import { HeroDetailComponent } from './hero-detail.component'; // #enddocregion hero-detail-import +import { HeroService } from './hero.service'; @Component({ selector: 'my-app', diff --git a/public/docs/_examples/toh-6/e2e-spec.js b/public/docs/_examples/toh-6/e2e-spec.js new file mode 100644 index 0000000000..02d5b2a698 --- /dev/null +++ b/public/docs/_examples/toh-6/e2e-spec.js @@ -0,0 +1,133 @@ +describe('TOH Http Chapter', function () { + + beforeEach(function () { + browser.get(''); + }); + + function getPageStruct() { + hrefEles = element.all(by.css('my-app a')); + + return { + hrefs: hrefEles, + myDashboardHref: hrefEles.get(0), + myDashboardParent: element(by.css('my-app my-dashboard')), + topHeroes: element.all(by.css('my-app my-dashboard .module.hero')), + + myHeroesHref: hrefEles.get(1), + myHeroesParent: element(by.css('my-app my-heroes')), + allHeroes: element.all(by.css('my-app my-heroes li .hero-element')), + + firstDeleteButton: element.all(by.buttonText('Delete')).get(0), + + addButton: element.all(by.buttonText('Add New Hero')).get(0), + + heroDetail: element(by.css('my-app my-hero-detail')) + } + } + + it('should be able to add a hero from the "Heroes" view', function(){ + var page = getPageStruct(); + var heroCount; + + page.myHeroesHref.click().then(function() { + browser.waitForAngular(); + heroCount = page.allHeroes.count(); + expect(heroCount).toBe(4, 'should show 4'); + }).then(function() { + return page.addButton.click(); + }).then(function(){ + return save(page,'','The New Hero'); + }).then(function(){ + browser.waitForAngular(); + + heroCount = page.allHeroes.count(); + expect(heroCount).toBe(5, 'should show 5'); + + var newHero = element(by.xpath('//span[@class="hero-element" and contains(text(),"The New Hero")]')); + expect(newHero).toBeDefined(); + }); + }); + + it('should be able to delete hero from "Heroes" view', function(){ + var page = getPageStruct(); + var heroCount; + + page.myHeroesHref.click().then(function() { + browser.waitForAngular(); + heroCount = page.allHeroes.count(); + expect(heroCount).toBe(4, 'should show 4'); + }).then(function() { + return page.firstDeleteButton.click(); + }).then(function(){ + browser.waitForAngular(); + heroCount = page.allHeroes.count(); + expect(heroCount).toBe(3, 'should show 3'); + }); + }); + + it('should be able to save details from "Dashboard" view', function () { + var page = getPageStruct(); + expect(page.myDashboardParent.isPresent()).toBe(true, 'dashboard element should be available'); + var heroEle = page.topHeroes.get(2); + var heroDescrEle = heroEle.element(by.css('h4')); + var heroDescr; + + return heroDescrEle.getText().then(function(text) { + heroDescr = text; + return heroEle.click(); + }).then(function() { + return save(page, heroDescr, '-foo'); + }) + .then(function(){ + return page.myDashboardHref.click(); + }) + .then(function() { + expect(page.myDashboardParent.isPresent()).toBe(true, 'dashboard element should be back'); + expect(heroDescrEle.getText()).toEqual(heroDescr + '-foo'); + }); + }); + + it('should be able to save details from "Heroes" view', function () { + var page = getPageStruct(); + + var viewDetailsButtonEle = page.myHeroesParent.element(by.cssContainingText('button', 'View Details')); + var heroEle, heroDescr; + + page.myHeroesHref.click().then(function() { + expect(page.myDashboardParent.isPresent()).toBe(false, 'dashboard element should NOT be present'); + expect(page.myHeroesParent.isPresent()).toBe(true, 'myHeroes element should be present'); + expect(viewDetailsButtonEle.isPresent()).toBe(false, 'viewDetails button should not yet be present'); + heroEle = page.allHeroes.get(0); + return heroEle.getText(); + }).then(function(text) { + // remove leading 'id' from the element + heroDescr = text.substr(text.indexOf(' ')+1); + return heroEle.click(); + }).then(function() { + expect(viewDetailsButtonEle.isDisplayed()).toBe(true, 'viewDetails button should now be visible'); + return viewDetailsButtonEle.click(); + }).then(function() { + return save(page, heroDescr, '-bar'); + }) + .then(function(){ + return page.myHeroesHref.click(); + }) + .then(function() { + expect(heroEle.getText()).toContain(heroDescr + '-bar'); + }); + }); + + function save(page, origValue, textToAdd) { + var inputEle = page.heroDetail.element(by.css('input')); + expect(inputEle.isDisplayed()).toBe(true, 'should be able to see the input box'); + var saveButtonEle = page.heroDetail.element(by.buttonText('Save')); + var backButtonEle = page.heroDetail.element(by.buttonText('Back')); + expect(backButtonEle.isDisplayed()).toBe(true, 'should be able to see the back button'); + var detailTextEle = page.heroDetail.element(by.css('div h2')); + expect(detailTextEle.getText()).toContain(origValue); + return sendKeys(inputEle, textToAdd).then(function () { + expect(detailTextEle.getText()).toContain(origValue + textToAdd); + return saveButtonEle.click(); + }); + } +}); diff --git a/public/docs/_examples/toh-6/ts/.gitignore b/public/docs/_examples/toh-6/ts/.gitignore new file mode 100644 index 0000000000..2cb7d2a2e9 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/.gitignore @@ -0,0 +1 @@ +**/*.js diff --git a/public/docs/_examples/toh-6/ts/app/app.component.css b/public/docs/_examples/toh-6/ts/app/app.component.css new file mode 100644 index 0000000000..137e9be7be --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/app.component.css @@ -0,0 +1,31 @@ +/* #docplaster */ +/* #docregion css */ +h1 { + font-size: 1.2em; + color: #999; + margin-bottom: 0; +} +h2 { + font-size: 2em; + margin-top: 0; + padding-top: 0; +} +nav a { + padding: 5px 10px; + text-decoration: none; + margin-top: 10px; + display: inline-block; + background-color: #eee; + border-radius: 4px; +} +nav a:visited, a:link { + color: #607D8B; +} +nav a:hover { + color: #039be5; + background-color: #CFD8DC; +} +nav a.router-link-active { + color: #039be5; +} +/* #enddocregion css */ diff --git a/public/docs/_examples/toh-6/ts/app/app.component.ts b/public/docs/_examples/toh-6/ts/app/app.component.ts new file mode 100644 index 0000000000..22317d4403 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/app.component.ts @@ -0,0 +1,36 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; +import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '@angular/router-deprecated'; + +import { DashboardComponent } from './dashboard.component'; +import { HeroesComponent } from './heroes.component'; +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroService } from './hero.service'; + +@Component({ + selector: 'my-app', + + template: ` +

{{title}}

+ + + `, + styleUrls: ['app/app.component.css'], + directives: [ROUTER_DIRECTIVES], + providers: [ + ROUTER_PROVIDERS, + HeroService, + ] +}) +@RouteConfig([ + { path: '/dashboard', name: 'Dashboard', component: DashboardComponent, useAsDefault: true }, + { path: '/detail/:id', name: 'HeroDetail', component: HeroDetailComponent }, + { path: '/heroes', name: 'Heroes', component: HeroesComponent } +]) +export class AppComponent { + title = 'Tour of Heroes'; +} diff --git a/public/docs/_examples/toh-6/ts/app/dashboard.component.css b/public/docs/_examples/toh-6/ts/app/dashboard.component.css new file mode 100644 index 0000000000..ce6e963a5f --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/dashboard.component.css @@ -0,0 +1,63 @@ +/* #docplaster */ +/* #docregion */ +[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; +} +.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; + } +} +/* #enddocregion */ diff --git a/public/docs/_examples/toh-6/ts/app/dashboard.component.html b/public/docs/_examples/toh-6/ts/app/dashboard.component.html new file mode 100644 index 0000000000..028eab6eb3 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/dashboard.component.html @@ -0,0 +1,11 @@ + +

Top Heroes

+
+ +
+ +
+

{{hero.name}}

+
+
+
diff --git a/public/docs/_examples/toh-6/ts/app/dashboard.component.ts b/public/docs/_examples/toh-6/ts/app/dashboard.component.ts new file mode 100644 index 0000000000..6070e76eb6 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/dashboard.component.ts @@ -0,0 +1,32 @@ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router-deprecated'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +@Component({ + selector: 'my-dashboard', + templateUrl: 'app/dashboard.component.html', + styleUrls: ['app/dashboard.component.css'] +}) +export class DashboardComponent implements OnInit { + + heroes: Hero[] = []; + + constructor( + private _router: Router, + private _heroService: HeroService) { + } + + ngOnInit() { + this._heroService.getHeroes() + .then(heroes => this.heroes = heroes.slice(1,5)); + } + + gotoDetail(hero: Hero) { + let link = ['HeroDetail', { id: hero.id }]; + this._router.navigate(link); + } +} diff --git a/public/docs/_examples/toh-6/ts/app/hero-detail.component.css b/public/docs/_examples/toh-6/ts/app/hero-detail.component.css new file mode 100644 index 0000000000..ab2437efd8 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/hero-detail.component.css @@ -0,0 +1,30 @@ +/* #docregion */ +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; +} +button:disabled { + background-color: #eee; + color: #ccc; + cursor: auto; +} diff --git a/public/docs/_examples/toh-6/ts/app/hero-detail.component.html b/public/docs/_examples/toh-6/ts/app/hero-detail.component.html new file mode 100644 index 0000000000..f532eb0109 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/hero-detail.component.html @@ -0,0 +1,13 @@ + + +
+

{{hero.name}} details!

+
+ {{hero.id}}
+
+ + +
+ + +
\ No newline at end of file diff --git a/public/docs/_examples/toh-6/ts/app/hero-detail.component.ts b/public/docs/_examples/toh-6/ts/app/hero-detail.component.ts new file mode 100644 index 0000000000..7b2889e592 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/hero-detail.component.ts @@ -0,0 +1,53 @@ +// #docplaster +// #docregion +import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core'; +import { RouteParams } from '@angular/router-deprecated'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; +@Component({ + selector: 'my-hero-detail', + templateUrl: 'app/hero-detail.component.html', + styleUrls: ['app/hero-detail.component.css'] +}) +export class HeroDetailComponent implements OnInit { + @Input() hero: Hero; + @Output() close = new EventEmitter(); + error: any; + navigated = false; // true if navigated here + + constructor( + private _heroService: HeroService, + private _routeParams: RouteParams) { + } + + // #docregion ngOnInit + ngOnInit() { + if (this._routeParams.get('id') !== null) { + let id = +this._routeParams.get('id'); + this.navigated = true; + this._heroService.getHero(id) + .then(hero => this.hero = hero); + } else { + this.navigated = false; + this.hero = new Hero(); + } + } + // #enddocregion ngOnInit + // #docregion save + save() { + this._heroService + .save(this.hero) + .then(hero => { + this.hero = hero; // saved hero, w/ id if new + this.goBack(hero); + }) + .catch(error => this.error = error); // TODO: Display error message + } + // #enddocregion save + goBack(savedHero: Hero = null) { + this.close.emit(savedHero); + if (this.navigated) { window.history.back(); } + } +} + diff --git a/public/docs/_examples/toh-6/ts/app/hero.service.ts b/public/docs/_examples/toh-6/ts/app/hero.service.ts new file mode 100644 index 0000000000..b4b0854141 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/hero.service.ts @@ -0,0 +1,99 @@ +// #docplaster +// #docregion +import { Injectable } from '@angular/core'; +import { Http, Headers } from '@angular/http'; + +// #docregion rxjs +import 'rxjs/add/operator/toPromise'; +// #enddocregion rxjs + +import { Hero } from './hero'; + +@Injectable() +export class HeroService { + + private heroesUrl = 'app/heroes'; // URL to web api + + constructor(private http: Http) { } + + // #docregion get-heroes + getHeroes(): Promise { + return this.http.get(this.heroesUrl) + // #docregion to-promise + .toPromise() + // #enddocregion to-promise + // #docregion to-data + .then(response => response.json().data) + // #enddocregion to-data + // #docregion catch + .catch(this.handleError); + // #enddocregion catch + } + // #enddocregion get-heroes + + getHero(id: number) { + return this.getHeroes() + .then(heroes => heroes.filter(hero => hero.id === id)[0]); + } + + // #docregion save + save(hero: Hero): Promise { + if (hero.id) { + return this.put(hero); + } + return this.post(hero); + } + // #enddocregion save + + // #docregion delete-hero + delete(hero: Hero) { + let headers = new Headers(); + headers.append('Content-Type', 'application/json'); + + let url = `${this.heroesUrl}/${hero.id}`; + + return this.http + .delete(url, headers) + .toPromise() + .catch(this.handleError); + } + // #enddocregion delete-hero + + // #docregion post-hero + // Add new Hero + private post(hero: Hero): Promise { + let headers = new Headers({ + 'Content-Type': 'application/json'}); + + return this.http + .post(this.heroesUrl, JSON.stringify(hero), {headers: headers}) + .toPromise() + .then(res => res.json().data) + .catch(this.handleError); + } + // #enddocregion post-hero + + // #docregion put-hero + // Update existing Hero + private put(hero: Hero) { + let headers = new Headers(); + headers.append('Content-Type', 'application/json'); + + let url = `${this.heroesUrl}/${hero.id}`; + + return this.http + .put(url, JSON.stringify(hero), {headers: headers}) + .toPromise() + .then(() => hero) + .catch(this.handleError); + } + // #enddocregion put-hero + + // #docregion error-handler + private handleError(error: any) { + console.error('An error occurred', error); + return Promise.reject(error.message || error); + } + // #enddocregion error-handler +} +// #enddocregion diff --git a/public/docs/_examples/toh-6/ts/app/hero.ts b/public/docs/_examples/toh-6/ts/app/hero.ts new file mode 100644 index 0000000000..e3eac516da --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/hero.ts @@ -0,0 +1,4 @@ +export class Hero { + id: number; + name: string; +} diff --git a/public/docs/_examples/toh-6/ts/app/heroes.component.css b/public/docs/_examples/toh-6/ts/app/heroes.component.css new file mode 100644 index 0000000000..35e45af98d --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/heroes.component.css @@ -0,0 +1,59 @@ +.selected { + background-color: #CFD8DC !important; + color: white; +} +.heroes { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 15em; +} +.heroes li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; +} +.heroes li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; +} +.heroes li.selected:hover { + background-color: #BBD8DC !important; + color: white; +} +.heroes .text { + position: relative; + top: -3px; +} +.heroes .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 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; +} diff --git a/public/docs/_examples/toh-6/ts/app/heroes.component.html b/public/docs/_examples/toh-6/ts/app/heroes.component.html new file mode 100644 index 0000000000..d29b6c93ed --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/heroes.component.html @@ -0,0 +1,21 @@ + +

My Heroes

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

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

+ +
diff --git a/public/docs/_examples/toh-6/ts/app/heroes.component.ts b/public/docs/_examples/toh-6/ts/app/heroes.component.ts new file mode 100644 index 0000000000..4de48b35fa --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/heroes.component.ts @@ -0,0 +1,66 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router-deprecated'; + +import { Hero } from './hero'; +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroService } from './hero.service'; + +@Component({ + selector: 'my-heroes', + templateUrl: 'app/heroes.component.html', + styleUrls: ['app/heroes.component.css'], + directives: [HeroDetailComponent] +}) +export class HeroesComponent implements OnInit { + heroes: Hero[]; + selectedHero: Hero; + addingHero = false; + error: any; + + constructor( + private _router: Router, + private _heroService: HeroService) { } + + getHeroes() { + this._heroService + .getHeroes() + .then(heroes => this.heroes = heroes) + .catch(error => this.error = error); // TODO: Display error message + } + + addHero() { + this.addingHero = true; + this.selectedHero = null; + } + + close(savedHero: Hero) { + this.addingHero = false; + if (savedHero) { this.getHeroes(); } + } + + // #docregion delete + delete(hero: Hero, event: any) { + event.stopPropagation(); + this._heroService + .delete(hero) + .then(res => { + this.heroes = this.heroes.filter(h => h.id !== hero.id); + }) + .catch(error => this.error = error); // TODO: Display error message + } + // #enddocregion delete + + ngOnInit() { + this.getHeroes(); + } + + onSelect(hero: Hero) { + this.selectedHero = hero; + this.addingHero = false; + } + + gotoDetail() { + this._router.navigate(['HeroDetail', { id: this.selectedHero.id }]); + } +} diff --git a/public/docs/_examples/toh-6/ts/app/in-memory-data.service.ts b/public/docs/_examples/toh-6/ts/app/in-memory-data.service.ts new file mode 100644 index 0000000000..0edb70f37b --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/in-memory-data.service.ts @@ -0,0 +1,12 @@ +// #docregion +export class InMemoryDataService { + createDb() { + let heroes = [ + { id: 1, name: 'Windstorm' }, + { id: 2, name: 'Bombasto' }, + { id: 3, name: 'Magneta' }, + { id: 4, name: 'Tornado' } + ]; + return {heroes}; + } +} diff --git a/public/docs/_examples/toh-6/ts/app/main.ts b/public/docs/_examples/toh-6/ts/app/main.ts new file mode 100644 index 0000000000..08caa81826 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/main.ts @@ -0,0 +1,29 @@ +// #docplaster +// #docregion final +// Imports for loading & configuring the in-memory web api +import { provide } from '@angular/core'; +import { XHRBackend } from '@angular/http'; + +import { InMemoryBackendService, SEED_DATA } from 'angular2-in-memory-web-api/core'; +import { InMemoryDataService } from './in-memory-data.service'; + +// The usual bootstrapping imports +// #docregion v1 +import { bootstrap } from '@angular/platform-browser-dynamic'; +import { HTTP_PROVIDERS } from '@angular/http'; + +import { AppComponent } from './app.component'; + +// #enddocregion v1, final +/* +// #docregion v1 +bootstrap(AppComponent, [ HTTP_PROVIDERS ]); +// #enddocregion v1 + */ +// #docregion final +bootstrap(AppComponent, [ + HTTP_PROVIDERS, + provide(XHRBackend, { useClass: InMemoryBackendService }), // in-mem server + provide(SEED_DATA, { useClass: InMemoryDataService }) // in-mem server data +]); +// #enddocregion final diff --git a/public/docs/_examples/toh-6/ts/example-config.json b/public/docs/_examples/toh-6/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/toh-6/ts/index.html b/public/docs/_examples/toh-6/ts/index.html new file mode 100644 index 0000000000..eb9ec157d8 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/index.html @@ -0,0 +1,27 @@ + + + + + Angular 2 Tour of Heroes + + + + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/toh-6/ts/plnkr.json b/public/docs/_examples/toh-6/ts/plnkr.json new file mode 100644 index 0000000000..777d9ad1f1 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Tour of Heroes: Part 6", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1,2].*" + ], + "tags": ["tutorial", "tour", "heroes", "http"] +} diff --git a/public/docs/_examples/toh-6/ts/sample.css b/public/docs/_examples/toh-6/ts/sample.css new file mode 100644 index 0000000000..042f0494f6 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/sample.css @@ -0,0 +1,8 @@ +button.delete-button{ + float:right; + background-color: gray !important; + color:white; +} + + + diff --git a/public/docs/ts/latest/tutorial/_data.json b/public/docs/ts/latest/tutorial/_data.json index 28d02b2ccb..22b7c4be4e 100644 --- a/public/docs/ts/latest/tutorial/_data.json +++ b/public/docs/ts/latest/tutorial/_data.json @@ -29,5 +29,10 @@ "title": "Routing", "intro": "We add the Angular Component Router and learn to navigate among the views", "nextable": true + }, + "toh-pt6": { + "title": "Http", + "intro": "We convert our service and components to use Http", + "nextable": true } } \ No newline at end of file diff --git a/public/docs/ts/latest/tutorial/toh-pt5.jade b/public/docs/ts/latest/tutorial/toh-pt5.jade index 1244b107ef..0afe4be3fc 100644 --- a/public/docs/ts/latest/tutorial/toh-pt5.jade +++ b/public/docs/ts/latest/tutorial/toh-pt5.jade @@ -669,6 +669,7 @@ figure.image-display .file index.html .file package.json .file styles.css + .file systemjs.config.json .file tsconfig.json .file typings.json :marked diff --git a/public/docs/ts/latest/tutorial/toh-pt6.jade b/public/docs/ts/latest/tutorial/toh-pt6.jade new file mode 100644 index 0000000000..5e0de751c9 --- /dev/null +++ b/public/docs/ts/latest/tutorial/toh-pt6.jade @@ -0,0 +1,350 @@ +include ../_util-fns + +:marked + # Http + + Our application has become a huge success and our stakeholders have expanded the vision to include integration with a hero api. + + The current solution limits us to a fixed set of heroes, but integration with a web server api will make our application much more flexible. We will also be able to add, edit and delete heroes. + + In this chapter we will connect our Angular 2 services to make http calls to our new api. +:marked + [Run the live example](/resources/live-examples/toh-6/ts/plnkr.html). + +.l-main-section +:marked + ## Where We Left Off + Before we continue with our Tour of Heroes, let’s verify that we have the following structure after adding our hero service + and hero detail component. If not, we’ll need to go back and follow the previous chapters. + +.filetree + .file angular2-tour-of-heroes + .children + .file app + .children + .file app.component.ts + .file app.component.css + .file dashboard.component.css + .file dashboard.component.html + .file dashboard.component.ts + .file hero.ts + .file hero-detail.component.css + .file hero-detail.component.html + .file hero-detail.component.ts + .file hero.service.ts + .file heroes.component.css + .file heroes.component.html + .file heroes.component.ts + .file main.ts + .file mock-heroes.ts + .file node_modules ... + .file typings ... + .file index.html + .file package.json + .file styles.css + .file systemjs.config.json + .file tsconfig.json + .file typings.json +:marked + ### Keep the app transpiling and running + Open a terminal/console window and enter the following command to + start the TypeScript compiler, start the server, and watch for changes: + +code-example(format="." language="bash"). + npm start + +:marked + The application runs and updates automatically as we continue to build the Tour of Heroes. + +.l-main-section +:marked + ## Prepare for Http + + `Http` is ***not*** a core Angular module. + It's Angular's optional approach to web access and it exists as a separate add-on module called `@angular/http`, + shipped in a separate script file as part of the Angular npm package. + + Fortunately we're ready to import from `@angular/http` because `systemjs.config` configured *SystemJS* to load that library when we need it. + +:marked + ### Register (provide) *http* services + Our app will depend upon the Angular `http` service which itself depends upon other supporting services. + The `HTTP_PROVIDERS` array from `@angular/http` library holds providers for the complete set of http services. + + We should be able to access these services from anywhere in the application. + So we register them in the `bootstrap` method of `main.ts` where we + launch the application and its root `AppComponent`. + ++makeExample('toh-6/ts/app/main.ts','v1','app/main.ts (v1)')(format='.') +:marked + Notice that we supply the `HTTP_PROVIDERS` in an array as the second parameter to the `bootstrap` method. + This has the same effect the `providers` array in `@Component` metadata. + +.l-main-section +:marked + ## Simulating the web api + + We generally recommend registering application-wide services in the root `AppComponent` *providers*. + Here we're registering in `main` for a special reason. + + Our application is in the early stages of development and far from ready for production. + We don't even have a web server that can handle requests for heroes. + Until we do, *we'll have to fake it*. + + We're going to *trick* the http client into fetching and saving data from + a demo/development service, the *in-memory web api*. + + The application itself doesn't need to know and shouldn't know about this. + So we'll slip the *in-memory web api* into the configuration *above* the `AppComponent`. + + Here is a version of `main` that performs this trick ++makeExample('toh-6/ts/app/main.ts', 'final', 'app/main.ts (final)')(format=".") + +:marked + We're replacing the default `XHRBackend`, the service that talks to the remote server, + with the *in-memory web api* service after priming it with the following `in-memory-data.service.ts` file: ++makeExample('toh-6/ts/app/in-memory-data.service.ts', null, 'app/in-memory-data.service.ts')(format=".") +:marked + This file replaces the `mock-heroes.ts` which is now safe to delete. + +.alert.is-helpful + :marked + This chaper is an introduction to the Angular http client. + Please don't be distracted by the details of this backend substitution. Just follow along with the example. + + Learn more later about the *in-memory web api* in the [Http chapter](../guide/server-communication.html#!#in-mem-web-api). + Remember, the *in-memory web api* is only useful in the early stages of development and for demonstrations such as this Tour of Heroes. + Skip it when you have a real web api server. + + +.l-main-section +:marked + ## Heroes and Http + + Look at our current `HeroService` implementation ++makeExample('toh-4/ts/app/hero.service.ts', 'get-heroes', 'app/hero.service.ts (getHeroes - old)')(format=".") +:marked + We returned a promise resolved with mock heroes. + It may have seemed like overkill at the time, but we were anticipating the + day when we fetched heroes with an http client and we knew that would have to be an asynchronous operation. + + That day has arrived! Let's convert `getHeroes()` to use Angular's `Http` client: + ++makeExample('toh-6/ts/app/hero.service.ts', 'get-heroes', 'app/hero.service.ts (getHeroes using Http)')(format=".") + +:marked + ### Http Promise + + We're still returning a promise but we're creating it differently. + + The Angular `http.get` returns an RxJS `Observable`. + *Observables* are a powerful way to manage asynchronous data flows. + We'll learn about `Observables` *later*. + + For *now* we get back on familiar ground by immediately converting that `Observable` to a `Promise` using the `toPromise` operator. ++makeExample('toh-6/ts/app/hero.service.ts', 'to-promise')(format=".") +:marked + Unfortunately, the Angular `Observable` doesn't have a `toPromise` operator ... not out of the box. + The Angular `Observable` is a bare-bones implementation. + + There are scores of operators like `toPromise` that extend `Observable` with useful capabilities. + If we want those capabilities, we have to add the operators ourselves. + That's as easy as importing them from the RxJS library like this: ++makeExample('toh-6/ts/app/hero.service.ts', 'rxjs')(format=".") + +:marked + ### Extracting the data in the *then* callback + In the *promise*'s `then` callback we call the `json` method of the http `Response` to extract the + data within the response. ++makeExample('toh-6/ts/app/hero.service.ts', 'to-data')(format=".") +:marked + That object returned by `json` has a single `data` property. + The `data` property holds the array of *heroes* that the caller really wants. + So we grab that array and return it as the resolved promise value. + +.alert.is-important + :marked + Pay close attention to the shape of the data returned by the server. + This particular *in-memory web api* example happens to return an object with a `data` property. + Your api might return something else. + + Adjust the code to match *your web api*. +:marked + The caller is unaware of these machinations. It receives a promise of *heroes* just as it did before. + It has no idea that we fetched the heroes from the server. + It knows nothing of the twists and turns required to turn the http response into heroes. + Such is the beauty and purpose of delegating data access to a service like this `HeroService`. +:marked + ### Error Handling + + At the end of `getHeroes` we `catch` server failures and pass them to an error handler: ++makeExample('toh-6/ts/app/hero.service.ts', 'catch')(format=".") +:marked + This is a critical step! + We must anticipate http failures as they happen frequently for reasons beyond our control. + ++makeExample('toh-6/ts/app/hero.service.ts', 'error-handler', 'app/hero.service.ts (Error handler)')(format=".") +:marked + In this demo service we log the error to the console; we should do better in real life. + + We've also decided to return a user friendly form of the error to + to the caller in a rejected promise so that the caller can display a proper error message to the user. + + ### Promises are Promises + Although we made significant *internal* changes to `getHeroes()`, the public signature did not change. + We still return a promise. We won't have to update any of the components that call `getHeroes()`. + +.l-main-section +:marked + ## Add, Edit, Delete + + Our stakeholders are incredibly pleased with the added flexibility from the api integration, but it doesn't stop there. Next we want to add the capability to add, edit and delete heroes. + + We'll complete `HeroService` by creating `post`, `put` and `delete` http calls to meet our new requirements. + +:marked + ### Post + + We are using `post` to add new heroes. Post requests require a little bit more setup than Get requests, but the format is as follows: + ++makeExample('toh-6/ts/app/hero.service.ts', 'post-hero', 'app/hero.service.ts (post hero)')(format=".") + +:marked + Now we create a header and set the content type to `application/json`. We'll call `JSON.stringify` before we post to convert the hero object to a string. + + ### Put + + `put` is used to edit a specific hero, but the structure is very similar to a `post` request. The only difference is that we have to change the url slightly by appending the id of the hero we want to edit. + ++makeExample('toh-6/ts/app/hero.service.ts', 'put-hero', 'app/hero.service.ts (put hero)')(format=".") + +:marked + ### Delete + `delete` is used to delete heroes and the format is identical to `put` except for the function name. + ++makeExample('toh-6/ts/app/hero.service.ts', 'delete-hero', 'app/hero.service.ts (delete hero)')(format=".") + +:marked + We add a `catch` to handle our errors for all three cases. + +:marked + ### Save + + We combine the call to the private `_post` and `_put` methods in a single `save` method. This simplifies the public api and makes the integration with `HeroDetailComponent` easier. `HeroService` determines which method to call based on the state of the `hero` object. If the hero already has an id we know it's an edit. Otherwise we know it's an add. + ++makeExample('toh-6/ts/app/hero.service.ts', 'save', 'app/hero.service.ts (save hero)')(format=".") + +:marked + After these additions our `HeroService` looks like this: + ++makeExample('toh-6/ts/app/hero.service.ts', null, 'app/hero.service.ts')(format=".") + +.l-main-section +:marked + ## Updating Components + + Loading heroes using `Http` required no changes outside of `HeroService`, but we added a few new features as well. In the following section we will update our components to use our new methods to add, edit and delete heroes. + + ### Add/Edit + We already have `HeroDetailComponent` for viewing details about a specific hero. Add and Edit are natural extensions of the detail view, so we are able to reuse `DetailHeroComponent` with a few tweaks. The original component was created to render existing data, but to add new data we have to initialize the `hero` property to an empty `Hero` object. + ++makeExample('toh-6/ts/app/hero-detail.component.ts', 'ngOnInit', 'app/hero-detail.component.ts (ngOnInit)')(format=".") + +:marked + In order to differentiate between add and edit we are adding a check to see if an id is passed in the url. If the id is absent we bind `HeroDetailComponent` to an empty `Hero` object. In either case, any edits made through the UI will be bound back to the same `hero` property. + + The next step is to add a save method to `HeroDetailComponent` and call the corresponding save method in `HeroesService`. + ++makeExample('toh-6/ts/app/hero-detail.component.ts', 'save', 'app/hero-detail.component.ts (save)')(format=".") + +:marked + The same save method is used for both add and edit since `HeroService` will know when to call `post` vs `put` based on the state of the `Hero` object. + + Earlier we used the `save()` method to return a promise, so when the promise resolves, we call `emit` to notify `HeroesComponent` that we just added or modified a hero. `HeroesComponent` is listening for this notification and will automatically refresh the list of heroes to include our recent updates. + +.l-sub-section + :marked + The `emit` "handshake" between `HeroDetailComponent` and `HeroesComponent` is an example of component to component communication. This is a topic for another day, but we have detailed information in our Component Interaction Cookbook + +:marked + Here is `HeroDetailComponent` with the added save button. + +figure.image-display + img(src='/resources/images/devguide/toh/hero-details-save-button.png' alt="Hero Details With Save Button") + +:marked + ### Delete + + We have added the option to delete hereos from `HeroesComponent`. `HeroService` will delete the hero, but we still have to filter out the deleted hero from the list to update the view. + ++makeExample('toh-6/ts/app/heroes.component.ts', 'delete', 'app/heroes.component.ts (delete)')(format=".") + +:marked + Here is `HeroesComponent` with the delete button. + +figure.image-display + img(src='/resources/images/devguide/toh/heroes-list-delete-button.png' alt="Heroes List With Delete Button") + +:marked + ### Review the App Structure + Let’s verify that we have the following structure after all of our good refactoring in this chapter: + +.filetree + .file angular2-tour-of-heroes + .children + .file app + .children + .file app.component.ts + .file app.component.css + .file dashboard.component.css + .file dashboard.component.html + .file dashboard.component.ts + .file hero.ts + .file hero-detail.component.css + .file hero-detail.component.html + .file hero-detail.component.ts + .file hero.service.ts + .file heroes.component.css + .file heroes.component.html + .file heroes.component.ts + .file main.ts + .file hero-data.service.ts + .file node_modules ... + .file typings ... + .file index.html + .file package.json + .file styles.css + .file sample.css + .file systemjs.config.json + .file tsconfig.json + .file typings.json + +.l-main-section +:marked + ## Home Stretch + + We are at the end of our journey for now, but we have accomplished a lot. + - We added the necessary dependencies to use Http in our application. + - We refactored HeroService to load heroes from an api. + - We extended HeroService to support post, put and delete calls. + - We updated our components to allow adding, editing and deleting of heroes. + - We configured an in-memory web api. + + Below is a summary of the files we changed. + ++makeTabs( + `toh-6/ts/app/app.component.ts, + toh-6/ts/app/heroes.component.ts, + toh-6/ts/app/heroes.component.html, + toh-6/ts/app/hero-detail.component.ts, + toh-6/ts/app/hero-detail.component.html, + toh-6/ts/app/hero.service.ts`, + null, + `app.comp...ts, + heroes.comp...ts, + heroes.comp...html, + hero-detail.comp...ts, + hero-detail.comp...html, + hero.service.ts` +) + diff --git a/public/resources/images/devguide/toh/hero-details-save-button.png b/public/resources/images/devguide/toh/hero-details-save-button.png new file mode 100644 index 0000000000000000000000000000000000000000..91c201a31078bf463f2ba3bb994abda37988525d GIT binary patch literal 17969 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 literal 0 HcmV?d00001 diff --git a/public/resources/images/devguide/toh/heroes-list-delete-button.png b/public/resources/images/devguide/toh/heroes-list-delete-button.png new file mode 100644 index 0000000000000000000000000000000000000000..da87a20e4a2016a5786bf2da4901b79d769ace8a GIT binary patch literal 17009 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 literal 0 HcmV?d00001