1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[和訳] 4. Multiple Components (Angular公式チュートリアル)

Posted at

この投稿は、Angular公式チュートリアルの 4. Multiple Components を和訳した記事です。
職場で行うAngular勉強会を円滑に進めることを目的としています。

筆者自身もAngularについては初級者で、英語の知識も乏しいため、記事内容への指摘がありましたら是非コメントをお願いします。

なお、この記事はAngular v4に対応したチュートリアルの和訳記事です。
最新版のチュートリアルではありませんのでご注意ください。

【チュートリアル一覧】
1. Introduction : 和訳ページ / 原文
2. The Hero Editor : 和訳ページ / 原文
3. Master/Detail : 和訳ページ / 原文
4. Multiple Components : このページです / 原文
5. Services : 和訳ページ作成中 / 原文
6. Routing : 和訳ページ作成中 / 原文
7. HTTP : 和訳ページ作成中 / 原文


Multiple Components

AppComponentは現時点ですべて処理を実行しています。
最初は、単一のヒーローの詳細を示しました。
その後、ヒーローのリストとヒーローの詳細の両方を持つ_Master/Detail_フォームになりました。
まもなく新しい要件と機能が生まれます。
しかし、1つのコンポーネントに機能を積み重ね続けることはできません。
なぜならメンテナンス性が低いからです。

特定の責務や機能に焦点を絞ったサブコンポーネントに分割する必要があります。
最終的に、AppComponentは、それらのサブコンポーネントを司るシンプルな骨格になる可能性があります。

このページでは、そういった方針へ向けた最初のステップとして、ヒーローの詳細ビューを再利用可能な別のコンポーネントに分割します。
このページが完成したら、アプリはこの例のようになります。
https://v4.angular.io/generated/live-examples/toh-pt3/eplnkr.html

前回どこで終わったか

このページを開始する前に、Tour of Heroesの初期の構造と同じ構造であることを確認してください。 そうでない場合は、前のページに戻ります。

スクリーンショット 2017-10-30 12.43.23.png

これまでと同じようにターミナルウ画面にnpm startコマンドを入力して、
Tour of Heroesを構築している間にアプリをコンパイル・実行し続けるようにしておいてください。

HeroDetailComponent(ヒーロー詳細コンポーネント)を作る

app/フォルダーにhero-detail.component.tsという名前のファイルを追加します。
このファイルには、新しいHeroDetailComponentというコンポーネントを持たせます。

ファイル名とコンポーネント名は、Angularスタイルガイドに記載されている標準に従います。

  • Componentクラス名は、アッパーキャメルケースで書かれ、 "Component"という単語で終わるべきです。 ヒーロー詳細コンポーネントクラス名はHeroDetailComponentです。
  • コンポーネントファイルの名前は、ダッシュ記号で綴る必要があります。各単語はダッシュで区切り、.component.tsで終わります。 HeroDetailComponentクラスはhero-detail.component.tsファイルに格納されます。

次のようにHeroDetailComponentを書き始めてみましょう。

app/hero-detail.component.ts(initial_version)
import { Component } from '@angular/core';

@Component({
  selector: 'hero-detail',
})
export class HeroDetailComponent {
}

コンポーネントを定義するには、常にComponentシンボルをimportします。

@Componentデコレータは、コンポーネントのAngluarメタデータを提供します。
hero-detailというCSSセレクタ名は、親コンポーネントのテンプレート内のこのコンポーネントを識別する要素タグと一致します。
このチュートリアルの最後で、<hero-detail>要素をAppComponentテンプレートに追加します。

コンポーネントを他の場所でもインポートできるようにするため、常にコンポーネントクラスをエクスポートしてください。

ヒーロー詳細テンプレート

ヒーロー詳細ビューをHeroDetailComponentに移動するには、ヒーロー詳細コンテンツをAppComponentテンプレートの下部から切り取り、@Componentメタデータの新しいtemplateプロパティに貼り付けます。

HeroDetailComponentは、「選択されたヒーロー」ではなく、「(1人の)ヒーロー」の情報を持ちます。
テンプレート内のすべてのselectedHeroという単語をheroという単語に置き換えます。
完了したら、新しいテンプレートは次のようになります。

src/app/hero-detail.component.ts(template)
@Component({
  selector: 'hero-detail',
  template: `
    <div *ngIf="hero">
      <h2>{{hero.name}} details!</h2>
      <div><label>id: </label>{{hero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
  `
})

heroプロパティを追加する

HeroDetailComponentのテンプレートは、コンポーネントのheroプロパティにバインドされます。
heroプロパティをHeroDetailComponentクラスに追加します。

src/app/hero-detail.component.ts(hero_property)
hero: Hero;

heroプロパティはHeroクラスのインスタンスとして型付けされます。
Heroクラスはまだapp.component.tsファイルにあります。
しかし今、Heroクラスを参照する必要がコンポーネントが2つになりました。
Angularのスタイルガイドでは、ファイルごとに1つのクラスを推奨しています。

Heroクラスをapp.component.tsから独自のhero.tsファイルに移動しましょう。

src/app/hero.ts
export class Hero {
  id: number;
  name: string;
}

Heroクラスが独自のファイルになったので、AppComponentHeroDetailComponentはそれをインポートする必要があります。
app.component.tsファイルとhero-detail.component.tsファイルの上部に次のimport文を追加します。

src/app/hero-detail.component.ts
import { Hero } from './hero';

heroプロパティは「入力」プロパティ

このページの後半では、親であるAppComponentが、選択されたヒーローをHeroDetailComponentheroプロパティにバインドすることによって、
表示するヒーローを子のHeroDetailComponentに伝えます。
バインディングする方法は次のようになります。

<hero-detail [hero]="selectedHero"></hero-detail>

heroプロパティの周りを角括弧で囲んで、等号(=)の左側に置くと、プロパティバインディング式の対象になります。
対象のバインディングプロパティ(今回の場合はhero)は子のHeroDetailComponentで「入力」プロパティとして宣言する必要があります。
それ以外の場合、Angularはバインディングを拒否し、エラーをスローします。

最初に、Inputシンボルをimportするために@angular/coreのimport文を修正します。

src/app/hero-detail.component.ts(抜粋)
import { Component, Input } from '@angular/core';

その後、importした@Inputデコレータをheroの宣言前に置くことで、heroが「入力」プロパティであることを宣言します。

src/app/hero-detail.component.ts(抜粋)
@Input() hero: Hero;

「入力」プロパティの詳細については、属性ディレクティブのページを参照してください。

これで終わりです。
heroプロパティは、HeroDetailComponentクラスが持つ唯一のプロパティです。

src/src/app/hero-detail.component.ts
export class HeroDetailComponent {
  @Input() hero: Hero;
}

HeroDetailComponentクラスが行うのは、「入力」プロパティであるheroプロパティでHeroオブジェクトを受け取った後、自身のテンプレートのheroプロパティにバインドすることだけです。

HeroDetailComponentの全体像は以下のようになります。

src/app/hero-detail.component.ts
import { Component, Input } from '@angular/core';

import { Hero } from './hero';
@Component({
  selector: 'hero-detail',
  template: `
    <div *ngIf="hero">
      <h2>{{hero.name}} details!</h2>
      <div><label>id: </label>{{hero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
  `
})
export class HeroDetailComponent {
  @Input() hero: Hero;
}

AppModuleHeroDetailComponentを宣言する

すべてのコンポーネントは、NgModuleで(NgModuleでのみ)宣言する必要があります。

エディタでapp.module.tsを開き、HeroDetailComponentを参照できるようにインポートします。

src/app/app.module.ts
import { HeroDetailComponent } from './hero-detail.component';

HeroDetailComponentをモジュールのdeclarations配列に追加します。

src/app/app.module.ts
declarations: [
  AppComponent,
  HeroDetailComponent
],

一般に、declarations配列には、モジュールに属するアプリケーションコンポーネント、パイプ、およびディレクティブのリストが含まれています。
あるコンポーネントが他のコンポーネントを参照するには、そのモジュール内でコンポーネントを宣言する必要があります。
このモジュールは、AppComponentHeroDetailComponentの2つのアプリケーションコンポーネントのみを宣言しています。

NgModulesの詳細については、NgModulesガイドを参照してください。

AppComponentHeroDetailComponentを追加する

AppComponentはまだmaster/detailビューのままです。
テンプレートのヒーロー詳細部分を切り取る前なので、ヒーロー詳細はまだAppComponent自身が表示しています。
今度はヒーロー詳細部分をHeroDetailComponentに委譲します。

hero-detailHeroDetailComponentメタデータのCSSセレクタであることを思い出してください。
これは、HeroDetailComponentを表す要素のタグ名です。

ヒーロー詳細ビューを使用していたAppComponentテンプレートの下部に<hero-detail>要素を追加します。

AppComponentselectedHeroプロパティをHeroDetailComponentheroプロパティにバインドして、
親であるAppComponentHeroDetailComponentと調整します。

app.component.ts(抜粋)
<hero-detail [hero]="selectedHero"></hero-detail>

selectedHeroが変更されるたびに、HeroDetailComponentは新しいヒーローを取得して表示します。

修正されたAppComponentのテンプレートは、次のようになります。

app.component.ts(抜粋)
template: `
  <h1>{{title}}</h1>
  <h2>My Heroes</h2>
  <ul class="heroes">
    <li *ngFor="let hero of heroes"
      [class.selected]="hero === selectedHero"
      (click)="onSelect(hero)">
      <span class="badge">{{hero.id}}</span> {{hero.name}}
    </li>
  </ul>
  <hero-detail [hero]="selectedHero"></hero-detail>
`,

何が変わったか?

以前同様、ユーザーがヒーロー名をクリックするたびに、ヒーローの詳細がヒーローリストの下に表示されます。
しかし今は、HeroDetailViewがその詳細を提示しています。

元のAppComponentを2つのコンポーネントに分けるリファクタリングは、現在、そして将来に渡って利益をもたらします。

  1. AppComponentの責任を軽減することによってAppComponentが単純化されました。
  2. 親であるAppComponentに手を入れることなく、HeroDetailComponentを豊かなヒーローエディタに進化させることができます。
  3. ヒーローの詳細ビューに触れることなく、AppComponentを進化させることができます。
  4. 将来の親コンポーネントのテンプレートでHeroDetailComponentを再利用することができます。

アプリの構造を確認する

次のファイル構成になっていることを確認しましょう。
スクリーンショット 2017-11-08 6.02.36.png

このページで解説したコードは次のとおりです。

src/app/hero-detail.component.ts
import { Component, Input } from '@angular/core';

import { Hero } from './hero';
@Component({
  selector: 'hero-detail',
  template: `
    <div *ngIf="hero">
      <h2>{{hero.name}} details!</h2>
      <div><label>id: </label>{{hero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
  `
})
export class HeroDetailComponent {
  @Input() hero: Hero;
}
src/app/app.component.ts
import { Component } from '@angular/core';

import { Hero } from './hero';

const HEROES: Hero[] = [
  { id: 11, name: 'Mr. Nice' },
  { id: 12, name: 'Narco' },
  { id: 13, name: 'Bombasto' },
  { id: 14, name: 'Celeritas' },
  { id: 15, name: 'Magneta' },
  { id: 16, name: 'RubberMan' },
  { id: 17, name: 'Dynama' },
  { id: 18, name: 'Dr IQ' },
  { id: 19, name: 'Magma' },
  { id: 20, name: 'Tornado' }
];

@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes"
        [class.selected]="hero === selectedHero"
        (click)="onSelect(hero)">
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
    <hero-detail [hero]="selectedHero"></hero-detail>
  `,
  styles: [`
    .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.selected:hover {
      background-color: #BBD8DC !important;
      color: white;
    }
    .heroes li:hover {
      color: #607D8B;
      background-color: #DDD;
      left: .1em;
    }
    .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;
    }
  `]
})
export class AppComponent {
  title = 'Tour of Heroes';
  heroes = HEROES;
  selectedHero: Hero;

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}
src/app/hero.ts
export class Hero {
  id: number;
  name: string;
}
src/app/app.module.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';

import { AppComponent }        from './app.component';
import { HeroDetailComponent } from './hero-detail.component';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule
  ],
  declarations: [
    AppComponent,
    HeroDetailComponent
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

まとめ

このページであなたが達成したことは次のとおりです:

  • 再利用可能なコンポーネントを作成しました。
  • コンポーネントが入力を受け入れる方法を学びました。
  • 必要なアプリケーションディレクティブをNgModuleで宣言することを学びました。@NgModuleデコレータのdeclarations配列にディレクティブを列挙しました。
  • 親コンポーネントを子コンポーネントにバインドする方法を学びました。

あなたのアプリはこの例のように見えるはずです。
https://v4.angular.io/generated/live-examples/toh-pt3/eplnkr.html

次のステップ

Tour of Heroesアプリは共有コンポーネントでより再利用しやすくなりましたが、
そのモックデータはまだAppComponent内でハードコードされています。
この状態は持続可能ではありません。
データアクセスは別のサービスにリファクタリングし、データを必要とするコンポーネント間で共有する必要があります。

次のチュートリアルページ(原文)でサービスを作成する方法を学びます。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?