From 5598ce83e22f21ac728c182582f05374996471fc Mon Sep 17 00:00:00 2001 From: Tsuyoshi HARA Date: Wed, 15 Nov 2017 13:52:32 +0900 Subject: [PATCH 1/2] translate: tutorial/toh-pt6 --- .../app/hero-search/hero-search.component.ts | 54 ++ .../src/app/heroes/heroes.component.html | 27 + .../examples/toh-pt6/src/hero.service.ts | 158 ++++++ aio-ja/content/tutorial/toh-pt6.md | 536 +++++++++--------- 4 files changed, 493 insertions(+), 282 deletions(-) create mode 100644 aio-ja/content/examples/toh-pt6/src/app/hero-search/hero-search.component.ts create mode 100644 aio-ja/content/examples/toh-pt6/src/app/heroes/heroes.component.html create mode 100644 aio-ja/content/examples/toh-pt6/src/hero.service.ts diff --git a/aio-ja/content/examples/toh-pt6/src/app/hero-search/hero-search.component.ts b/aio-ja/content/examples/toh-pt6/src/app/hero-search/hero-search.component.ts new file mode 100644 index 0000000000..8f1770420d --- /dev/null +++ b/aio-ja/content/examples/toh-pt6/src/app/hero-search/hero-search.component.ts @@ -0,0 +1,54 @@ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; + +// #docregion rxjs-imports +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/Subject'; +import { of } from 'rxjs/observable/of'; + +import { + debounceTime, distinctUntilChanged, switchMap + } from 'rxjs/operators'; +// #enddocregion rxjs-imports + +import { Hero } from '../hero'; +import { HeroService } from '../hero.service'; + +@Component({ + selector: 'app-hero-search', + templateUrl: './hero-search.component.html', + styleUrls: [ './hero-search.component.css' ] +}) +export class HeroSearchComponent implements OnInit { + // #docregion heroes-stream + heroes$: Observable; + // #enddocregion heroes-stream + // #docregion searchTerms + private searchTerms = new Subject(); + // #enddocregion searchTerms + + constructor(private heroService: HeroService) {} + // #docregion searchTerms + + // 検索語をobservableストリームにpushする + search(term: string): void { + this.searchTerms.next(term); + } + // #enddocregion searchTerms + + ngOnInit(): void { + // #docregion search + this.heroes$ = this.searchTerms.pipe( + // 各キーストロークの後、検索前に300ms待つ + debounceTime(300), + + // 直前の検索語と同じ場合は無視する + distinctUntilChanged(), + + // 検索語が変わる度に、新しい検索observableにスイッチする + switchMap((term: string) => this.heroService.searchHeroes(term)), + ); + // #enddocregion search + } +} diff --git a/aio-ja/content/examples/toh-pt6/src/app/heroes/heroes.component.html b/aio-ja/content/examples/toh-pt6/src/app/heroes/heroes.component.html new file mode 100644 index 0000000000..9db9ce7da9 --- /dev/null +++ b/aio-ja/content/examples/toh-pt6/src/app/heroes/heroes.component.html @@ -0,0 +1,27 @@ +

My Heroes

+ + +
+ + + +
+ + + + + diff --git a/aio-ja/content/examples/toh-pt6/src/hero.service.ts b/aio-ja/content/examples/toh-pt6/src/hero.service.ts new file mode 100644 index 0000000000..ed735c3fdb --- /dev/null +++ b/aio-ja/content/examples/toh-pt6/src/hero.service.ts @@ -0,0 +1,158 @@ +// #docplaster +// #docregion +import { Injectable } from '@angular/core'; +// #docregion import-httpclient +import { HttpClient, HttpHeaders } from '@angular/common/http'; +// #enddocregion import-httpclient + +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; +// #docregion import-rxjs-operators +import { catchError, map, tap } from 'rxjs/operators'; +// #enddocregion import-rxjs-operators + +import { Hero } from './hero'; +import { MessageService } from './message.service'; + +// #docregion http-options +const httpOptions = { + headers: new HttpHeaders({ 'Content-Type': 'application/json' }) +}; +// #enddocregion http-options + +@Injectable() +export class HeroService { + + // #docregion heroesUrl + private heroesUrl = 'api/heroes'; // Web APIのURL + // #enddocregion heroesUrl + + // #docregion ctor + constructor( + private http: HttpClient, + private messageService: MessageService) { } + // #enddocregion ctor + + // #docregion getHeroes, getHeroes-1 + /** サーバーからヒーローを取得する */ + // #docregion getHeroes-2 + getHeroes (): Observable { + return this.http.get(this.heroesUrl) + // #enddocregion getHeroes-1 + .pipe( + // #enddocregion getHeroes-2 + tap(heroes => this.log(`fetched heroes`)), + // #docregion getHeroes-2 + catchError(this.handleError('getHeroes', [])) + ); + // #docregion getHeroes-1 + } + // #enddocregion getHeroes, getHeroes-1, getHeroes-2 + + // #docregion getHeroNo404 + /** IDによりヒーローを取得. idが見つからない場合は`undefined`を返す */ + getHeroNo404(id: number): Observable { + const url = `${this.heroesUrl}/?id=${id}`; + return this.http.get(url) + .pipe( + map(heroes => heroes[0]), // returns a {0|1} element array + // #enddocregion getHeroNo404 + tap(h => { + const outcome = h ? `fetched` : `did not find`; + this.log(`${outcome} hero id=${id}`); + }), + catchError(this.handleError(`getHero id=${id}`)) + // #docregion getHeroNo404 + ); + } + // #enddocregion getHeroNo404 + + // #docregion getHero + /** IDによりヒーローを取得。見つからなかった場合は404を返却 */ + getHero(id: number): Observable { + const url = `${this.heroesUrl}/${id}`; + return this.http.get(url).pipe( + tap(_ => this.log(`fetched hero id=${id}`)), + catchError(this.handleError(`getHero id=${id}`)) + ); + } + // #enddocregion getHero + + // #docregion searchHeroes + /* 検索語を含むヒーローを取得する */ + searchHeroes(term: string): Observable { + if (!term.trim()) { + // 検索語がない場合、空のヒーロー配列を返す + return of([]); + } + return this.http.get(`api/heroes/?name=${term}`).pipe( + tap(_ => this.log(`found heroes matching "${term}"`)), + catchError(this.handleError('searchHeroes', [])) + ); + } + // #enddocregion searchHeroes + + //////// Save methods ////////// + + // #docregion addHero + /** POST: サーバーに新しいヒーローを登録する */ + addHero (hero: Hero): Observable { + return this.http.post(this.heroesUrl, hero, httpOptions).pipe( + tap((hero: Hero) => this.log(`added hero w/ id=${hero.id}`)), + catchError(this.handleError('addHero')) + ); + } + // #enddocregion addHero + + // #docregion deleteHero + /** DELETE: サーバーからヒーローを削除 */ + deleteHero (hero: Hero | number): Observable { + const id = typeof hero === 'number' ? hero : hero.id; + const url = `${this.heroesUrl}/${id}`; + + return this.http.delete(url, httpOptions).pipe( + tap(_ => this.log(`deleted hero id=${id}`)), + catchError(this.handleError('deleteHero')) + ); + } + // #enddocregion deleteHero + + // #docregion updateHero + /** PUT: サーバー上でヒーローを更新 */ + updateHero (hero: Hero): Observable { + return this.http.put(this.heroesUrl, hero, httpOptions).pipe( + tap(_ => this.log(`updated hero id=${hero.id}`)), + catchError(this.handleError('updateHero')) + ); + } + // #enddocregion updateHero + + // #docregion handleError + /** + * 失敗したHttp操作を処理します + * アプリを持続させます。 + * @param operation - 失敗した操作の名前 + * @param result - observableな結果として返す任意の値 + */ + private handleError (operation = 'operation', result?: T) { + return (error: any): Observable => { + + // TODO: リモート上のロギング基盤にエラーを送信する + console.error(error); // かわりにconsoleに出力 + + // TODO: ユーザーへの開示のためにエラーの変換処理を改善する + this.log(`${operation} failed: ${error.message}`); + + // 空の結果を返して、アプリを持続可能にする + return of(result as T); + }; + } + // #enddocregion handleError + + // #docregion log + /** HeroServiceのメッセージをMessageServiceを使って記録 */ + private log(message: string) { + this.messageService.add('HeroService: ' + message); + } + // #enddocregion log +} diff --git a/aio-ja/content/tutorial/toh-pt6.md b/aio-ja/content/tutorial/toh-pt6.md index aa65f928f1..f1527a6719 100644 --- a/aio-ja/content/tutorial/toh-pt6.md +++ b/aio-ja/content/tutorial/toh-pt6.md @@ -1,409 +1,389 @@ # HTTP -In this tutorial, you'll add the following data persistence features with help from -Angular's `HttpClient`. +このチュートリアルではAngularの`HttpClient`を使用して、次のデータ永続の機能を追加します。 -* The `HeroService` gets hero data with HTTP requests. -* Users can add, edit, and delete heroes and save these changes over HTTP. -* Users can search for heroes by name. +* `HeroService`はHTTPリクエストを介してヒーローデータを取得します。 +* ユーザーはヒーロー情報を追加、編集、削除ができ、その変更をHTTPを通して保存することができます。 +* ユーザーは名前でヒーロー情報を検索できます。 -When you're done with this page, the app should look like this . +このページを終えたとき、このアプリは次のようになります。 -## Enable HTTP services +## HTTPサービスの有効化 -`HttpClient` is Angular's mechanism for communicating with a remote server over HTTP. +`HttpClient` はHTTPを通してリモートサーバーと通信するための仕組みです。 -To make `HttpClient` available everywhere in the app, +アプリ内のどこでも`HttpClient`を利用可能にするには -* open the root `AppModule`, -* import the `HttpClientModule` symbol from `@angular/common/http`, -* add it to the `@NgModule.imports` array. +* ルート`AppModule`を開く +* `@angular/common/http`から`HttpClientModule`をインポート +* `@NgModule.imports`の配列に`HttpClientModule`を追加 -## Simulate a data server +## データサーバーをシミュレート -This tutorial sample _mimics_ communication with a remote data server by using the -[_In-memory Web API_](https://github.com/angular/in-memory-web-api "In-memory Web API") module. +本チュートリアルでは [_In-memory Web API_](https://github.com/angular/in-memory-web-api "インメモリWebAPI")モジュール +を利用してリモートデータサーバーとの通信を再現します。 -After installing the module, the app will make requests to and receive responses from the `HttpClient` -without knowing that the *In-memory Web API* is intercepting those requests, -applying them to an in-memory data store, and returning simulated responses. +このモジュールをインストールすると、アプリは*インメモリWeb API*がリクエストをインターセプトして、そのリクエストを +インメモリデータストアに適用し、シミュレートされたレスポンスを返すということを知らずに +`HttpClient`を使ってリクエストを送信し、レスポンスを受信することができます。 -This facility is a great convenience for the tutorial. -You won't have to set up a server to learn about `HttpClient`. +この機能は本チュートリアルにおいて非常に有用なものです。 +`HttpClient`を学ぶにあたり、サーバーを用意する必要がないためです。 -It may also be convenient in the early stages of your own app development when -the server's web api is ill-defined or not yet implemented. +また、サーバーWebAPIが明確に定義されていなかったり、未実装だったりする開発の初期段階においても便利です。
-**Important:** the *In-memory Web API* module has nothing to do with HTTP in Angular. +**重要** *インメモリWeb API*モジュールはAngularのHTTPとは関係がありません。 -If you're just _reading_ this tutorial to learn about `HttpClient`, you can [skip over](#import-heroes) this step. -If you're _coding along_ with this tutorial, stay here and add the *In-memory Web API* now. +`HttpClient`を学ぶためにこのチュートリアルを読んでいるのであれば、このステップを[読み飛ばして](#import-heroes)ください。 +本チュートリアルとともにコーディングしている場合は、ここで*インメモリWeb API*を追加しましょう。
-Install the *In-memory Web API* package from _npm_ +_npm_から*インメモリWeb API*をインストールします。 npm install angular-in-memory-web-api --save -Import the `InMemoryWebApiModule` and the `InMemoryDataService` class, -which you will create in a moment. +`InMemoryWebApiModule`とこれからすぐ作成する`InMemoryDataService`クラスをインポートします。 + title="src/app/app.module.ts (インメモリ Web API をインポート)"> -Add the `InMemoryWebApiModule` to the `@NgModule.imports` array— -_after importing the `HttpClient`_, -—while configuring it with the `InMemoryDataService`. +`InMemoryWebApiModule`を`@NgModule.imports`の配列に追加します。 +—_`HttpClient`をインポートしたあとに_— +さらにそれを`InMemoryDataService`で設定します。 -The `forRoot()` configuration method takes an `InMemoryDataService` class -that primes the in-memory database. +`forRoot()`メソッドはインメモリデータベースを用意する`InMemoryDataService`クラスを引数に取ります。 -The _Tour of Heroes_ sample creates such a class -`src/app/in-memory-data.service.ts` which has the following content: +_Tour of Heroes_サンプルでは次のような内容の`src/app/in-memory-data.service.ts`を作ります。 -This file replaces `mock-heroes.ts`, which is now safe to delete. +このファイルは`mock-heros.ts`の置き換えなので、`mock-heros.ts`は削除してしまっても問題ありません。 -When your server is ready, detach the *In-memory Web API*, and the app's requests will go through to the server. +サーバーが準備されたら、*インメモリWeb API*を外せば、アプリのリクエストはサーバーに送信されます。 -Now back to the `HttpClient` story. +それでは`HttpClient`の話に戻りましょう。 {@a import-heroes} -## Heroes and HTTP +## ヒーローとHTTP -Import some HTTP symbols that you'll need: +必要なHTTPシンボルをいくつかインポートします。 + title="src/app/hero.service.ts (HTTPシンボルをインポート)"> -Inject `HttpClient` into the constructor in a private property called `http`. +`HttpClient`をプライベートプロパティ`http`にコンストラクタで注入します。 -Keep injecting the `MessageService`. You'll call it so frequently that -you'll wrap it in private `log` method. +`MessageService`はそのままにしておきます。 +これは頻繁に呼ぶことになるので、プライベートメソッド`log`にラップしておきましょう。 -Define the `heroesUrl` with the address of the heroes resource on the server. +`heroesUrl`をサーバー上のヒーローリソースのアドレスで定義します。 -### Get heroes with _HttpClient_ +### _HttpClient_を使ってヒーローを取得 -The current `HeroService.getHeroes()` -uses the RxJS `of()` function to return an array of mock heroes -as an `Observable`. +現在の`HeroService.getHeroes()`はRxJSの`of()`を使って、モックのヒーロー配列を`Observable`として返します。 + title="src/app/hero.service.ts (RxJSの'of()'を使ったgetHeroes)"> -Convert that method to use `HttpClient` +このメソッドを`HttpClient`を使うように改修します。 + -Refresh the browser. The hero data should successfully load from the -mock server. +ブラウザをリフレッシュしましょう。ヒーローデータがモックサーバーから正しくロードされているはずです。 + +`http.get`と`of`を取り替えましたが、 +どちらの関数も`Observable`を返すので、 +アプリは他の変更を加えずに動作しています。 -You've swapped `http.get` for `of` and the app keeps working without any other changes -because both functions return an `Observable`. +### Httpメソッドはひとつの値を返す -### Http methods return one value +すべての`HttpClient`メソッドはRxJSの`Observable`を返します。 -All `HttpClient` methods return an RxJS `Observable` of something. +HTTPはリクエスト/レスポンスプロトコルです。 +リクエストを送信すると、ひとつのレスポンスを返却します。 -HTTP is a request/response protocol. -You make a request, it returns a single response. +一般には、`Observable`は時間によって複数の値を返すことができます。 -In general, an `Observable` _can_ return multiple values over time. -An `Observable` from `HttpClient` always emits a single value and then completes, never to emit again. +`HttpClient`が返す`Observable`は常にひとつの値を発行してから完了するので、再び値を発行することはありません。 -This particular `HttpClient.get` call returns an `Observable`, literally "_an observable of hero arrays_". In practice, it will only return a single hero array. +特に今回の`HttpClient.get`の呼び出しは`Observable`、逐語的には「_ヒーローの配列のobservable_」です。 +実際やってみると、ひとつのヒーロー配列を返します。 -### _HttpClient.get_ returns response data +### _HttpClient.get_はレスポンスデータを返す -`HttpClient.get` returns the _body_ of the response as an untyped JSON object by default. -Applying the optional type specifier, `` , gives you a typed result object. +`HttpClient.get`はデフォルトではレスポンスの本文を型のないJSONで返します。 +特定の型 `` 指定をすると、その型のオブジェクトを返します。 -The shape of the JSON data is determined by the server's data API. -The _Tour of Heroes_ data API returns the hero data as an array. +JSONデータの形はサーバーのデータAPIにより決まります。 +_Tour of Heroes_のデータAPIはヒーロー情報を配列で返します。
-Other APIs may bury the data that you want within an object. -You might have to dig that data out by processing the `Observable` result -with the RxJS `map` operator. +他のAPIでは取得したいデータがオブジェクトの中に埋もれているかもしれません。 +その場合は、RxJSの`map`を使って`Observable`を処理してデータを掘り出します。 -Although not discussed here, there's an example of `map` in the `getHeroNo404()` -method included in the sample source code. +ここでは取り扱いませんが、サンプルソースコード内にある`getHeroNo404()`メソッドに`map`の例があります。
-### Error handling +### エラーハンドリング -Things go wrong, especially when you're getting data from a remote server. -The `HeroService.getHeroes()` method should catch errors and do something appropriate. +リモートサーバーからのデータを受け取るときに、何かうまくいかない場合。 +`HeroService.getHeroes()`メソッドはエラーをキャッチして、適切に対応する必要があります。 -To catch errors, you **"pipe" the observable** result from `http.get()` through an RxJS `catchError()` operator. +エラーをキャッチするには`http.get()`で得られた`observerable`な結果をRxJSの`catchError()`オペレーターに**連結**します。 -Import the `catchError` symbol from `rxjs/operators`, along with some other operators you'll need later. +あとで必要になるオペレーターと一緒に、`rxjs/operators`から`catchError`をインポートします。 -Now extend the observable result with the `.pipe()` method and -give it a `catchError()` operator. +それではobservableの結果を`pipe()`で拡張して、それを`catchError()`オペレーターに渡しましょう。 -The `catchError()` operator intercepts an **`Observable that failed**. -It passes the error an _error handler_ that can do what it wants with the error. +`catchError()`オペレーターは**失敗したObservable**をインターセプトします。 +これはエラーをそれを処理する_error handler_に渡します。 -The following `handleError()` method reports the error and then returns an -innocuous result so that the application keeps working. +次の`handleError()`メソッドはエラーを報告し、アプリを動作し続けるために無害な結果を返します。 #### _handleError_ -The following `errorHandler()` will be shared by many `HeroService` methods -so it's generalized to meet their different needs. +次の`errorHandler()`は数々の`HeroService`メソッドで共有されるので、様々な要件を満たすために一般化されています。 -Instead of handling the error directly, it returns an _error handler_ function to `catchError` that it -has configured with both the name of the operation that failed and a safe return value. +エラーを直接ハンドリングするかわりに、処理に失敗した処理の名前と、安全な返却値の両方で構成された +_error handler_関数を`catchError`に返します。 -After reporting the error to console, the handler constructs -a user friendly message and returns a safe value to the app so it can keep working. +エラーをコンソールに出力したあと、ハンドラーはユーザフレンドリーなメッセージを生成し、アプリを +動作し続けるための安全な値を返却します。 -Because each service method returns a different kind of `Observable` result, -`errorHandler()` takes a type parameter so it can return the safe value as the type that the app expects. +サービスの各メソッドはそれぞれ違う種類の`Observable`な結果を返すため、 +`errorHandler()`は型パラメーターを取り、アプリが期待する型の値を返却できます。 -### Tap into the _Observable_ +### _Observable_に侵入 -The `HeroService` methods will **tap** into the flow of observable values -and send a message (via `log()`) to the message area at the bottom of the page. +`HeroService`のメソットはObservableな値の流れに入り込んで、(`log()`を通して)ページ下部にメッセージを送信します。 -They'll do that with the RxJS `tap` operator, -which _looks_ at the observable values, does _something_ with those values, -and passes them along. -The `tap` call back doesn't touch the values themselves. +これはRxJSの`tap`オペレーターを使って行います。 +これはObservableな値を見て、その値に何か処理を行い、それらを渡します。 +`tap()`コールバックは、値そのものには触れません。 -Here is the final version of `getHeroes` with the `tap` that logs the operation. +次が、ログ処理を`tap`した`getHeroes`の最終版です。 -### Get hero by id +### IDでヒーローを取得 -Most web APIs support a _get by id_ request in the form `api/hero/:id` -(such as `api/hero/11`). -Add a `HeroService.getHero()` method to make that request: +ほとんどのWeb APIは `api/hero/:id` (`api/hero/11`のような) 形式のリクエストで_IDにより取得する_ことをサポートしています。 + +`HeroService.getHero()`メソッドを追加して、そのようなリクエストができるようにしましょう: -There are three significant differences from `getHeroes()`. +`getHeroes()`とくらべて3つ重要な違いがあります。 -* it constructs a request URL with the desired hero's id. -* the server should respond with a single hero rather than an array of heroes. -* therefore, `getHero` returns an `Observable` ("_an observable of Hero objects_") - rather than an observable of hero _arrays_ . +* 求めたいヒーローのIDを含んだURLを生成すること。 +* サーバーはヒーローたちの配列ではなく、一人のヒーローの情報を返す必要があること。 +* したがって、`getHero`はヒーローの配列のObservableを返すのではなく、 +`Observable` (_ヒーローオブジェクトのObservable_)を返すこと。 +## ヒーローを更新 ## Update heroes -Editing a hero's name in the _hero detail_ view. -As you type, the hero name updates the heading at the top of the page. -But when you click the "go back button", the changes are lost. +_ヒーロー詳細_画面で、ヒーローの名前を編集します。 +タイプすると、ページ上部のヒーローの名前が更新されます。 +ですが、"go back button"をクリックすると、その変更は失われてしまいます。 -If you want changes to persist, you must write them back to -the server. +その変更を永続化したい場合は、それをサーバーに送り返す必要があります。 -At the end of the hero detail template, add a save button with a `click` event -binding that invokes a new component method named `save()`. +ヒーロー詳細ページのテンプレートの終わりに、新しい`save()`というメソッドを呼び出す保存ボタンを追加します。 - + -Add the following `save()` method, which persists hero name changes using the hero service -`updateHero()` method and then navigates back to the previous view. +hero serviceの`updateHero()`を呼び出して名前の変更を永続化したのち、前のビューに戻る`save()`メソッドを追加しましょう。 - + -#### Add _HeroService.updateHero()_ +#### _HeroService.updateHero()_の追加 -The overall structure of the `updateHero()` method is similar to that of -`getHeroes()`, but it uses `http.put()` to persist the changed hero -on the server. +`updateHero()`メソッドの全体の構造は`getHeroes()`のそれと似ていますが、 +こちらではサーバー上でヒーローの変更を永続化するために`http.put()`を使用しています。 + title="src/app/hero.service.ts (更新)"> -The `HttpClient.put()` method takes three parameters -* the URL -* the data to update (the modified hero in this case) -* options +`HttpClient.put()`メソッドは3つのパラメーターを取ります。 + +* URL +* アップデート用のデータ (今回の場合は編集されたヒーロー) +* オプション -The URL is unchanged. The heroes web API knows which hero to update by looking at the hero's `id`. +URLは変わりません。 +ヒーローWeb APIはヒーローの`id`を見てどのヒーローを更新すべきかを知ります。 -The heroes web API expects a special header in HTTP save requests. -That header is in the `httpOption` constant defined in the `HeroService`. +ヒーローWeb APIはHTTPの保存リクエストのとき特別なヘッダーを期待します。 +そのヘッダーは`HeroService`の定数`httpOption`で定義されています。 -Refresh the browser, change a hero name, save your change, -and click the "go back" button. -The hero now appears in the list with the changed name. +ブラウザをリフレッシュして、ヒーローの名前を変更して、変更を保存し、"go back"ボタンをクリックしてください。 +リストに変更された名前のヒーローが現れているはずです。 -## Add a new hero +## 新しいヒーローを追加 -To add a hero, this app only needs the hero's name. You can use an `input` -element paired with an add button. +ヒーローを追加するためには、このアプリではヒーローの名前だけ必要です。 +追加ボタンとペアになった`input`要素が使えます。 -Insert the following into the `HeroesComponent` template, just after -the heading: +下記を`HeroesComponent`のテンプレートの先頭に挿入します。 - + -In response to a click event, call the component's click handler and then -clear the input field so that it's ready for another name. +クリックイベントに反応し、コンポーネントのクリックハンドラーが呼び出され、そのあと次の名前の入力を可能にするためにinputフィールドをクリアします。 - + -When the given name is non-blank, the handler creates a `Hero`-like object -from the name (it's only missing the `id`) and passes it to the services `addHero()` method. +与えられた名前が空文字でない場合、ハンドラーは名前から(`id`だけが抜けた)`Hero`-ライクなオブジェクトを作成し、 +サービスの`addHero()`メソッドに渡します。 -When `addHero` saves successfully, the `subscribe` callback -receives the new hero and pushes it into to the `heroes` list for display. +`addHero`が正常に登録すると、`subscribe`コールバックが新しいヒーローを受取、表示用の`heroes`リストに追加します。 -You'll write `HeroService.addHero` in the next section. +`HeroService.addHero`は次のセクションで書きます。 -#### Add _HeroService.addHero()_ +#### _HeroService.addHero()_の追加 -Add the following `addHero()` method to the `HeroService` class. +`HeroService`クラスに次のメソッド`addHero()`を追加します。 -`HeroService.addHero()` differs from `updateHero` in two ways. +`HeroService.addHero()`は`updateHero`と2つ違う点があります。 -* it calls `HttpClient.post()` instead of `put()`. -* it expects the server to generates an id for the new hero, -which it returns in the `Observable` to the caller. +* `put()`の代わりに`HttpClient.post()`を呼び出します。 +* サーバーで新しいヒーローのIDが生成されることを期待します。そしてそれは呼び出し元に`Observable`として戻ります。 -Refresh the browser and add some heroes. +ブラウザをリフレッシュして、いくつかヒーローを登録しましょう。 -## Delete a hero +## ヒーローを削除 -Each hero in the heroes list should have a delete button. +リスト内の各ヒーローは削除ボタンを持つべきです。 -Add the following button element to the `HeroesComponent` template, after the hero -name in the repeated `
  • ` element. +次のボタンを`HeroesComponent`のテンプレートに追加します。 +繰り返されている`
  • `エレメント内のヒーロー名の後ろです。 -The HTML for the list of heroes should look like this: +ヒーローたちのリストのHTMLは次のようになるはずです。 - + -To position the delete button at the far right of the hero entry, -add some CSS to the `heroes.component.css`. You'll find that CSS -in the [final review code](#heroescomponent) below. +削除ボタンをヒーロー項目の右寄りに配置するためには、`heroes.component.css`にCSSを追加します。 +どのようなCSSになるのかは、下部の[最終的なコードレビュー](#heroescomponent)で見ることができます。 -Add the `delete()` handler to the component. +`delete()`ハンドラーをコンポーネントに追加しましょう。 - + -Although the component delegates hero deletion to the `HeroService`, -it remains responsible for updating its own list of heroes. -The component's `delete()` method immediately removes the _hero-to-delete_ from that list, -anticipating that the `HeroService` will succeed on the server. +削除処理は`HeroService`に任されますが、コンポーネントでもそれ自身がもつヒーローリストの更新処理は必要です。 +コンポーネント側の`delete()`メソッドは`HeroService`がサーバーとの処理を成功するものと予測して、 +_削除されるべきヒーロー_をリストから即座に削除します。 -There's really nothing for the component to do with the `Observable` returned by -`heroService.delete()`. **It must subscribe anyway**. +`heroService.delete()`が返却する`Observable`に対してはコンポーネント側で何もする必要はありません。 +**いずれにしろsubscribeはしなければなりません**
    - If you neglect to `subscribe()`, the service will not send the delete request to the server! - As a rule, an `Observable` _does nothing_ until something subscribes! - - Confirm this for yourself by temporarily removing the `subscribe()`, - clicking "Dashboard", then clicking "Heroes". - You'll see the full list of heroes again. + もし`subscribe()`をし忘れると、サービスはDELETEリクエストをサーバーに送信しません! + ルールとして、`Observable`はsubscribeされるまで_なにもしません_ + + これを確認するためには、一時的に`subscribe()`を外して、 + "Dashboard"をクリックし、それから"Heroes"をクリックしてください。 + そうするとヒーローのフルリストが再び現れるはずです。
    -#### Add _HeroService.deleteHero()_ +#### _HeroService.deleteHero()_の追加 -Add a `deleteHero()` method to `HeroService` like this. +次のように`HeroService`にメソッド`deleteHero()`を追加しましょう。 - + -Note that +下記に注目 -* it calls `HttpClient.delete`. -* the URL is the heroes resource URL plus the `id` of the hero to delete -* you don't send data as you did with `put` and `post`. -* you still send the `httpOptions`. +* `HttpClient.delete`を実行。 +* URLはヒーローリソースのURLと削除するヒーローの`id` +* `put`や`post`で行っていたようなデータ送信はしません。 +* `httpOptions`は送信しています。 -Refresh the browser and try the new delete functionality. +ブラウザをリフレッシュして、新しい削除機能を試しましょう。 -## Search by name +## 名前で検索 -In this last exercise, you learn to chain `Observable` operators together -so you can minimize the number of similar HTTP requests -and consume network bandwidth economically. +この最後のエクササイズでは、`Observable`オペレーターをチェーンを学んで、 +同じようなHTTPリクエストの数を減らし、効率よくネットワーク帯域を使えるようにします。 -You will add a *heroes search* feature to the *Dashboard*. -As the user types a name into a search box, -you'll make repeated HTTP requests for heroes filtered by that name. -Your goal is to issue only as many requests as necessary. +*Dashboard*に*ヒーロー検索*の機能をつけましょう。 +ユーザーが検索ボックスに名前をタイプすると、 +その名前でフィルターするHTTPリクエストを繰り返し送信します。 +ゴールは必要最低限のリクエストを送信することです。 #### _HeroService.searchHeroes_ -Start by adding a `searchHeroes` method to the `HeroService`. +`HeroService`に`searchHeroes`メソッドを追加するところから始めましょう。 -The method returns immediately with an empty array if there is no search term. -The rest of it closely resembles `getHeroes()`. -The only significant difference is the URL, -which includes a query string with the search term. +検索ワードがない場合、このメソッドはただちに空の配列を返します。 +それ以外の場合は`getHeroes()`ととても似ています。 +ただひとつの重要な違いはURLで、検索ワードがついたクエリ文字列を含んでいます。 -### Add search to the Dashboard +### ダッシュボードに検索を追加 + +`DashboardComponent`のテンプレートを開いて、ヒーロー検索のエレメント、``をテンプレートの下部に追加します。 Open the `DashboardComponent` _template_ and Add the hero search element, ``, to the bottom of the `DashboardComponent` template. @@ -425,150 +406,139 @@ Add the hero search element, ``, to the bottom of the `Dashboar path="toh-pt6/src/app/dashboard/dashboard.component.html" title="src/app/dashboard/dashboard.component.html" linenums="false"> -This template looks a lot like the `*ngFor` repeater in the `HeroesComponent` template. +このテンプレートは`HeroesComponent`のテンプレートの`*ngFor`にとても似ています。 -Unfortunately, adding this element breaks the app. -Angular can't find a component with a selector that matches ``. +残念ながら、この要素を追加することでアプリが壊れます。 +Angularが`app-hero-search`にマッチするコンポーネントを見つけることができないためです。 -The `HeroSearchComponent` doesn't exist yet. Fix that. +`HeroSearchComponent`がまだありません。直しましょう。 -### Create _HeroSearchComponent_ +### _HeroSearchComponent_作成 -Create a `HeroSearchComponent` with the CLI. +CLIで`HeroSearchComponent`を作ります。 ng generate component hero-search -The CLI generates the three `HeroSearchComponent` and adds the component to the `AppModule' declarations +CLIは`HeroSearchComponent`を作成し、`AppModule`のdeclarationsにそのコンポーネントを追加します。 -Replace the generated `HeroSearchComponent` _template_ with a text box and a list of matching search results like this. +作成された`HeroSearchComponent`のテンプレートを次のようにテキストボックスと、マッチした検索結果一覧を表示するように書き換えます。 -Add private CSS styles to `hero-search.component.css` -as listed in the [final code review](#herosearchcomponent) below. +下にある[final code review](#herosearchcomponent)にあるようにプライベートCSSスタイルを`hero-search.component.css`を追加します。 -As the user types in the search box, a *keyup* event binding calls the component's `search()` -method with the new search box value. +ユーザーが検索ボックス内でタイプすると、*keyup*イベントのバインディングが +コンポーネントの`search()`メソッドを呼び出して、検索ボックス内の新しい値を渡します。 {@a asyncpipe} ### _AsyncPipe_ -As expected, the `*ngFor` repeats hero objects. +予想どおり、`*ngFor`がヒーローオブジェクトを繰り返します。 -Look closely and you'll see that the `*ngFor` iterates over a list called `heroes$`, not `heroes`. +よく見ると、`*ngFor`は`heroes`ではなく`heroes$`を繰り返していることに気づくでしょう。 -The `$` is a convention that indicates `heroes$` is an `Observable`, not an array. +`$`は`heroes$`が配列ではなく`Observable`であることを示すために慣例でつけられるものです。 -The `*ngFor` can't do anything with an `Observable`. -But there's also a pipe character (`|`) followed by `async`, -which identifies Angular's `AsyncPipe`. +`*ngFor`が`Observable`について何もできません。 +しかしパイプ文字(`|`)に続く`async`もあり、これはAngularの`AsyncPipe`を意味します。 -The `AsyncPipe` subscribes to an `Observable` automatically so you won't have to -do so in the component class. +`AsyncPipe`は自動的に`Observable`をsubscribeするので、コンポーネントクラスで何もする必要はありません。 -### Fix the _HeroSearchComponent_ class +### _HeroSearchComponent_クラスの修正 -Replace the generated `HeroSearchComponent` class and metadata as follows. +生成された`HeroSearchComponent`クラスとメタデータを次のように置き換えます。 -Notice the declaration of `heroes$` as an `Observable` +`heroes$`の宣言が`Observable`であることに注意 -You'll set it in [`ngOnInit()`](#search-pipe). -Before you do, focus on the definition of `searchTerms`. +これを[`ngOnInit()`](#search-pipe)内でセットします。 +これをする前に、`searchTerms`の定義に注目しましょう。 -### The _searchTerms_ RxJS subject +### RxJS subjectの_searchTerms_ -The `searchTerms` property is declared as an RxJS `Subject`. +`searchTerms`プロパティはRxJSの`Subject`として定義されています。 -A `Subject` is both a source of _observable_ values and an `Observable` itself. -You can subscribe to a `Subject` as you would any `Observable`. +`Subject`は_observable_な値の元でもあり、`Observable`そのものでもあります。 +`Observable`にするように`Subject`をsubscribeすることができます。 -You can also push values into that `Observable` by calling its `next(value)` method -as the `search()` method does. +また、`search()`メソッドで実行している様に、`next(value)`メソッドを呼ぶことで、`Observable`に値をpushすることができます。 -The `search()` method is called via an _event binding_ to the -textbox's `keystroke` event. +`search()`メソッドはテキストボックスの`keystroke`のイベントバインディングにより呼び出されます。 -Every time the user types in the textbox, the binding calls `search()` with the textbox value, a "search term". -The `searchTerms` becomes an `Observable` emitting a steady stream of search terms. +テキストボックス内でユーザーがタイプする度、テキストボックスの値、検索語と共に`search()`を呼び出します。 +`searchTerms`は検索語の安定したストリームを発行する`Observable`になります。 {@a search-pipe} -### Chaining RxJS operators +### RxJSオペレーターのチェーン -Passing a new search term directly to the `searchHeroes()` after every user keystroke would create an excessive amount of HTTP requests, -taxing server resources and burning through the cellular network data plan. +ユーザーのすべてのキーストロークの度に`searchHeroes()`に新しい検索語を渡していては、 +極端に多いHTTPリクエストを送信することになり、サーバーリソースを圧迫したり、 +セルラー方式ネットワークのデータプランをすぐに溶かしてしまいます。 -Instead, the `ngOnInit()` method pipes the `searchTerms` _observable_ through a sequence of RxJS operators that reduce the number of calls to the `searchHeroes()`, -ultimately returning an _observable_ of timely hero search results (each a `Hero[]`). +代わりに、`ngOnInit()`メソッドが`searchTerms`_observable_を`searchHeroes()`を呼ぶ回数を抑えるための +いくつかのRxJSオペレーターをつなげていて、 +最終的にヒーローのタイムリーな検索結果の_observable_(それぞれは`Hero[]`)を返します。 -Here's the code. +次がそのコードです。 +* `debounceTime(300)`は最新の文字列を渡す前に、新しい文字列の入力を300ミリ秒待ちます。 +* `distinctUntilChanged`はフィルター用の文字列が変わったときだけリクエストを送信することを保証します。 -* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds -before passing along the latest string. You'll never make requests more frequently than 300ms. - - -* `distinctUntilChanged` ensures that a request is sent only if the filter text changed. - - -* `switchMap()` calls the search service for each search term that makes it through `debounce` and `distinctUntilChanged`. -It cancels and discards previous search observables, returning only the latest search service observable. - +* `switchMap()`は`debounce`と`distinctUntilChanged`を通り抜けた各検索語について検索サービスを呼び出します。 +これは破棄された直前のobservableらをキャンセルして、最新の検索サービスのobservableのみを返却します。
    + + [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html)により + すべての適格なキーイベントが`HttpClient.get`メソッドを呼び出すことができます。 + 各リクエスト間の300msの休止により、複数のHTTPリクエストを送信できますが、それらは順序どおりに戻ってこないかもしれません。 - With the [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html), - every qualifying key event can trigger an `HttpClient.get()` method call. - Even with a 300ms pause between requests, you could have multiple HTTP requests in flight - and they may not return in the order sent. - - `switchMap()` preserves the original request order while returning only the observable from the most recent HTTP method call. - Results from prior calls are canceled and discarded. + `swtichMap()`は元のリクエスト順を保持しますが、最も新しいHTTPメソッドコールからのobservableのみを返します。 + 前の呼び出しはキャンセルされ、破棄されます。 - Note that _canceling_ a previous `searchHeroes()` _Observable_ - doesn't actually abort a pending HTTP request. - Unwanted results are simply discarded before they reach your application code. + 前の`searchHeroes`の_Observable_を_キャンセルする_というのは実際には保留中のHTTPリクエストを中止しているということに注意してください。 + 不本意な結果はアプリケーションのコードに到達する前に破棄されます。
    -Remember that the component _class_ does not subscribe to the `heroes$` _observable_. -That's the job of the [`AsyncPipe`](#asyncpipe) in the template. +コンポーネント_クラス_が`heroes$`_observable_をsubscribeしていないことを思い出してください。 +それはテンプレート内の[`AsyncPipe`](#asyncpipe)の役割です。 -#### Try it +#### 試しましょう -Run the app again. In the *Dashboard*, enter some text in the search box. -If you enter characters that match any existing hero names, you'll see something like this. +アプリを再度起動しましょう。*Dashboard*にて、検索ボックスで何かテキストを入力してください。 +ヒーロー名にマッチするような文字を入力すると、こんなふうに見えるはずです。
    Hero Search Component
    -## Final code review +## 最終的なコードレビュー -Your app should look like this . +アプリはこののようになっているはずです。 -Here are the code files discussed on this page (all in the `src/app/` folder). +これがこのページで説明していたコードファイルです。(すべて`src/app/`フォルダーの中にあります) {@a heroservice} {@a inmemorydataservice} @@ -640,16 +610,18 @@ Here are the code files discussed on this page (all in the `src/app/` folder). -## Summary +## まとめ + +旅はここで終わりです。あなたは多くのことを成し遂げました。 + +* アプリにHTTPで必要な依存パッケージを追加しました。 +* `HeroService`をリファクタリングして、Web APIからヒーローを読み込めるようにしました。 +* `HeroService`を拡張して、`post()`, `put()`, そして `delete()`メソッドを使えるようにしました。 +* コンポーネントを更新して、ヒーローを追加、更新、そして削除できるようにしました。 +* インメモリWeb APIを設定しました。 +* Observableをどのように扱うかを学びました。 -You're at the end of your journey, and you've accomplished a lot. -* You added the necessary dependencies to use HTTP in the app. -* You refactored `HeroService` to load heroes from a web API. -* You extended `HeroService` to support `post()`, `put()`, and `delete()` methods. -* You updated the components to allow adding, editing, and deleting of heroes. -* You configured an in-memory web API. -* You learned how to use Observables. +これで"Tour of Heroes"のチュートリアルは終了です。 +基礎ガイドのセクションで、さらにAngularでの開発について学べるようになりました。 +[Architecture](guide/architecture "Architecture")ガイドから始めましょう。 -This concludes the "Tour of Heroes" tutorial. -You're ready to learn more about Angular development in the fundamentals section, -starting with the [Architecture](guide/architecture "Architecture") guide. From b9c838ae95aaeb45060fb6fd7e5bbace7043d3d9 Mon Sep 17 00:00:00 2001 From: Tsuyoshi HARA Date: Thu, 16 Nov 2017 13:39:36 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E3=81=AE=E5=8F=8D=E6=98=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aio-ja/content/tutorial/toh-pt6.md | 38 +++++++++++++----------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/aio-ja/content/tutorial/toh-pt6.md b/aio-ja/content/tutorial/toh-pt6.md index f1527a6719..8bf9e99ebf 100644 --- a/aio-ja/content/tutorial/toh-pt6.md +++ b/aio-ja/content/tutorial/toh-pt6.md @@ -6,7 +6,7 @@ * ユーザーはヒーロー情報を追加、編集、削除ができ、その変更をHTTPを通して保存することができます。 * ユーザーは名前でヒーロー情報を検索できます。 -このページを終えたとき、このアプリは次のようになります。 +このページを終えたとき、このアプリは次のようになります。 ## HTTPサービスの有効化 @@ -14,11 +14,11 @@ アプリ内のどこでも`HttpClient`を利用可能にするには -* ルート`AppModule`を開く +* ルートの`AppModule`を開く * `@angular/common/http`から`HttpClientModule`をインポート * `@NgModule.imports`の配列に`HttpClientModule`を追加 -## データサーバーをシミュレート +## データサーバーをシミュレートする 本チュートリアルでは [_In-memory Web API_](https://github.com/angular/in-memory-web-api "インメモリWebAPI")モジュール を利用してリモートデータサーバーとの通信を再現します。 @@ -109,7 +109,7 @@ _Tour of Heroes_サンプルでは次のような内容の`src/app/in-memory-dat region="heroesUrl" > -### _HttpClient_を使ってヒーローを取得 +### _HttpClient_を使ってヒーローを取得する 現在の`HeroService.getHeroes()`はRxJSの`of()`を使って、モックのヒーロー配列を`Observable`として返します。 @@ -209,7 +209,7 @@ _error handler_関数を`catchError`に返します。 ### _Observable_に侵入 -`HeroService`のメソットはObservableな値の流れに入り込んで、(`log()`を通して)ページ下部にメッセージを送信します。 +`HeroService`のメソッドはObservableな値の流れに入り込んで、(`log()`を通して)ページ下部にメッセージを送信します。 これはRxJSの`tap`オペレーターを使って行います。 これはObservableな値を見て、その値に何か処理を行い、それらを渡します。 @@ -222,7 +222,7 @@ _error handler_関数を`catchError`に返します。 region="getHeroes" > -### IDでヒーローを取得 +### IDでヒーローを取得する ほとんどのWeb APIは `api/hero/:id` (`api/hero/11`のような) 形式のリクエストで_IDにより取得する_ことをサポートしています。 @@ -237,8 +237,7 @@ _error handler_関数を`catchError`に返します。 * したがって、`getHero`はヒーローの配列のObservableを返すのではなく、 `Observable` (_ヒーローオブジェクトのObservable_)を返すこと。 -## ヒーローを更新 -## Update heroes +## ヒーローを更新する _ヒーロー詳細_画面で、ヒーローの名前を編集します。 タイプすると、ページ上部のヒーローの名前が更新されます。 @@ -285,7 +284,7 @@ URLは変わりません。 ブラウザをリフレッシュして、ヒーローの名前を変更して、変更を保存し、"go back"ボタンをクリックしてください。 リストに変更された名前のヒーローが現れているはずです。 -## 新しいヒーローを追加 +## 新しいヒーローを追加する ヒーローを追加するためには、このアプリではヒーローの名前だけ必要です。 追加ボタンとペアになった`input`要素が使えます。 @@ -318,7 +317,7 @@ URLは変わりません。 ブラウザをリフレッシュして、いくつかヒーローを登録しましょう。 -## ヒーローを削除 +## ヒーローを削除する リスト内の各ヒーローは削除ボタンを持つべきです。 @@ -362,7 +361,7 @@ _削除されるべきヒーロー_をリストから即座に削除します。 -下記に注目 +下記に注目しましょう * `HttpClient.delete`を実行。 * URLはヒーローリソースのURLと削除するヒーローの`id` @@ -371,7 +370,7 @@ _削除されるべきヒーロー_をリストから即座に削除します。 ブラウザをリフレッシュして、新しい削除機能を試しましょう。 -## 名前で検索 +## 名前で検索する この最後のエクササイズでは、`Observable`オペレーターをチェーンを学んで、 同じようなHTTPリクエストの数を減らし、効率よくネットワーク帯域を使えるようにします。 @@ -399,9 +398,6 @@ _削除されるべきヒーロー_をリストから即座に削除します。 `DashboardComponent`のテンプレートを開いて、ヒーロー検索のエレメント、``をテンプレートの下部に追加します。 -Open the `DashboardComponent` _template_ and -Add the hero search element, ``, to the bottom of the `DashboardComponent` template. - @@ -413,7 +409,7 @@ Angularが`app-hero-search`にマッチするコンポーネントを見つけ `HeroSearchComponent`がまだありません。直しましょう。 -### _HeroSearchComponent_作成 +### _HeroSearchComponent_を作成する CLIで`HeroSearchComponent`を作ります。 @@ -444,18 +440,18 @@ CLIは`HeroSearchComponent`を作成し、`AppModule`のdeclarationsにそのコ `$`は`heroes$`が配列ではなく`Observable`であることを示すために慣例でつけられるものです。 -`*ngFor`が`Observable`について何もできません。 +`*ngFor`は`Observable`について何もできません。 しかしパイプ文字(`|`)に続く`async`もあり、これはAngularの`AsyncPipe`を意味します。 `AsyncPipe`は自動的に`Observable`をsubscribeするので、コンポーネントクラスで何もする必要はありません。 -### _HeroSearchComponent_クラスの修正 +### _HeroSearchComponent_クラスを修正する 生成された`HeroSearchComponent`クラスとメタデータを次のように置き換えます。 -`heroes$`の宣言が`Observable`であることに注意 +`heroes$`の宣言が`Observable`であることに注意しましょう。 @@ -484,7 +480,7 @@ CLIは`HeroSearchComponent`を作成し、`AppModule`のdeclarationsにそのコ {@a search-pipe} -### RxJSオペレーターのチェーン +### RxJSオペレーターの連結 ユーザーのすべてのキーストロークの度に`searchHeroes()`に新しい検索語を渡していては、 極端に多いHTTPリクエストを送信することになり、サーバーリソースを圧迫したり、 @@ -506,7 +502,7 @@ CLIは`HeroSearchComponent`を作成し、`AppModule`のdeclarationsにそのコ * `distinctUntilChanged`はフィルター用の文字列が変わったときだけリクエストを送信することを保証します。 * `switchMap()`は`debounce`と`distinctUntilChanged`を通り抜けた各検索語について検索サービスを呼び出します。 -これは破棄された直前のobservableらをキャンセルして、最新の検索サービスのobservableのみを返却します。 +これはそれまでの検索のobservableをキャンセルし、最新の検索サービスのobservableだけを返します。