第2回 Angular勉強会 〜楽曲検索アプリ作成〜

[前回](https://qiita.com/C3REVE/items/967bd2a26a1e0c648955)に続き、今回は [第2回「Webフロントエンド開発勉強会」](https://qiita.com/C3REVE/items/fb03a4ca57b1fc246ca3) で作成した楽曲検索アプリを Angular で実装することを目的として、コンポーネント、サービス、HTTP通信について説明します。

[第1回 Angular勉強会](https://qiita.com/C3REVE/items/967bd2a26a1e0c648955)を実施していない方は事前に実施しておいてください。

# 前回の復習

検索ボタンを押したら固定の楽曲情報を表示するページを作成してください。

```bash
$ ng generate component study2/music-search
```

```app-routing.module.ts
import { EcSiteComponent } from './study2/ec-site/ec-site.component';

const routes: Routes = [

  :

  { path: 'study2/music-search', component: MusicSearchComponent },
];
```

```index.component.html
<h1>第１回 Angular勉強会</h1>
<ul>
  <li><a routerLink="/type-script">TypeScript</a></li>
  <li><a routerLink="/data-binding">data-binding</a></li>
  <li><a routerLink="/pipe">pipe</a></li>
  <li><a routerLink="/directive">directive</a></li>
</ul>

<h1>第２回 Angular勉強会</h1>
<ul>
  <li><a routerLink="/study2/music-search">music-search</a></li>
</ul>
```

```music-search.component.html
<div>
  <button>検索</button>
  <div id="resultContent">
  </div>
</div>
```

```music-search.component.ts
import { Component, OnInit } from '@angular/core';

class MusicItem {
  trackName: string;
  artistName: string;
  artworkUrl100: string;
}

@Component({
  selector: 'app-music-search',
  templateUrl: './music-search.component.html',
  styleUrls: ['./music-search.component.css']
})
export class MusicSearchComponent implements OnInit {


  constructor() { }

  ngOnInit() {
  }

  /**
   * 検索実行
   */
  onSearch() {
    const results = [
      { artistName: 'アーティスト名１', trackName: '楽曲名１', artworkUrl100: 'https://is5-ssl.mzstatic.com/image/thumb/Music6/v4/a5/df/97/a5df97ec-b7e4-7f78-625a-f331603b0756/source/100x100bb.jpg' },
      { artistName: 'アーティスト名２', trackName: '楽曲名２', artworkUrl100: 'https://is4-ssl.mzstatic.com/image/thumb/Music/v4/fb/d8/04/fbd8047c-293e-1f90-4b06-929bbeca20b1/source/100x100bb.jpg' },
      { artistName: 'アーティスト名３', trackName: '楽曲名３', artworkUrl100: 'https://is2-ssl.mzstatic.com/image/thumb/Music/v4/4d/2e/39/4d2e39ef-fd24-a902-e704-cb7dc2ae6c5b/source/100x100bb.jpg' },
      { artistName: 'アーティスト名４', trackName: '楽曲名４', artworkUrl100: 'https://is5-ssl.mzstatic.com/image/thumb/Music/v4/54/ff/d6/54ffd6b2-3f3f-ce92-189d-20c3143b2380/source/100x100bb.jpg' },
      { artistName: 'アーティスト名５', trackName: '楽曲名５', artworkUrl100: 'https://is2-ssl.mzstatic.com/image/thumb/Music6/v4/ae/4a/fd/ae4afdd5-0dc7-31a4-ab96-eacc806a32d5/source/100x100bb.jpg' }
    ];
  }
}
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158277/52294b4c-b430-1450-715d-133b1b562439.png)

<details><summary>答え</summary><div>

```music-search.component.ts
import { Component, OnInit } from '@angular/core';

class MusicItem {
  trackName: string;
  artistName: string;
  artworkUrl100: string;
}

@Component({
  selector: 'app-music-search',
  templateUrl: './music-search.component.html',
  styleUrls: ['./music-search.component.css']
})
export class MusicSearchComponent implements OnInit {

  results: Array<MusicItem>;

  constructor() { }

  ngOnInit() {
  }

  /**
   * 検索実行
   */
  onSearch() {
    this.results = [
      { artistName: 'アーティスト名１', trackName: '楽曲名１', artworkUrl100: 'https://is5-ssl.mzstatic.com/image/thumb/Music6/v4/a5/df/97/a5df97ec-b7e4-7f78-625a-f331603b0756/source/100x100bb.jpg' },
      { artistName: 'アーティスト名２', trackName: '楽曲名２', artworkUrl100: 'https://is4-ssl.mzstatic.com/image/thumb/Music/v4/fb/d8/04/fbd8047c-293e-1f90-4b06-929bbeca20b1/source/100x100bb.jpg' },
      { artistName: 'アーティスト名３', trackName: '楽曲名３', artworkUrl100: 'https://is2-ssl.mzstatic.com/image/thumb/Music/v4/4d/2e/39/4d2e39ef-fd24-a902-e704-cb7dc2ae6c5b/source/100x100bb.jpg' },
      { artistName: 'アーティスト名４', trackName: '楽曲名４', artworkUrl100: 'https://is5-ssl.mzstatic.com/image/thumb/Music/v4/54/ff/d6/54ffd6b2-3f3f-ce92-189d-20c3143b2380/source/100x100bb.jpg' },
      { artistName: 'アーティスト名５', trackName: '楽曲名５', artworkUrl100: 'https://is2-ssl.mzstatic.com/image/thumb/Music6/v4/ae/4a/fd/ae4afdd5-0dc7-31a4-ab96-eacc806a32d5/source/100x100bb.jpg' }
    ];
  }
}
```

```music-search.component.html
<div>
  <button (click)="onSearch()">検索</button>
  <div id="resultContent">
    <div *ngFor="let item of results">
      <img [src]="item?.artworkUrl100">
      <div class="track-name">{{item?.trackName}}</div>
      <div class="artist-name">{{item?.artistName}}</div>
    </div>
  </div>
</div>
```

```music-search.component.css
.track-name {
  font-weight: bold;
}
```
</div></details>


# コンポーネント

HTMLの要素をカプセル化して再利用可能なパーツを提供するための仕組みをコンポーネントといいます。

これまで作ってきた各ページもコンポーネントですが、コンポーネントを複数組み合わせて利用することで、それぞれが持つ機能や役割が明確になったり、パーツの再利用が可能になったりします。

以下のコンポーネントを作成してページに追加してください。

```bash
$ ng generate component study2/ec-site
$ ng generate component study2/product
```

```app-routing.module.ts
import { EcSiteComponent } from './study2/ec-site/ec-site.component';

const routes: Routes = [

  :

  { path: 'study2/ec-site', component: EcSiteComponent },
];
```

```index.component.html
<h1>第１回 Angular勉強会</h1>
<ul>
  <li><a routerLink="/type-script">TypeScript</a></li>
  <li><a routerLink="/data-binding">data-binding</a></li>
  <li><a routerLink="/pipe">pipe</a></li>
  <li><a routerLink="/directive">directive</a></li>
</ul>

<h1>第２回 Angular勉強会</h1>
<ul>
  <li><a routerLink="/study2/ec-site">ec-site</a></li>
</ul>
```

## コンポーネントを作成する

ECサイトの商品を想定した商品コンポーネント `ProductComponent` を作成してみましょう。

`product.component.ts` の `selector: 'app-product'` の部分が、`ProductComponent` を利用するためのセレクタ名（タグ名）となり、`@Input('product') product: ProductInfo;` が、このコンポーネントへ渡すプロパティとなります。
コンポーネントを利用する場合は、 `<app-product [product]="商品オブジェクト"></app-product>` のように指定します。

```product.component.ts
import { Component, OnInit, Input } from '@angular/core';

export class ProductInfo {
  id: number;
  name: string;
  image?: string;
}

@Component({
  selector: 'app-product',
  templateUrl: './product.component.html',
  styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {

  @Input('product') product: ProductInfo;

  constructor() { }

  ngOnInit() {
  }

}
```

```product.component.html
<div class="product">
  <img [src]="product?.image">
  <p>{{product?.name}}</p>
</div>
```

```product.component.css
.product {
    width: 130px;
    margin: 8px;
    padding: 8px;
    border: 1px solid gray;
    background-color: #f7f7f7;
    text-align: center;
}
```

```ec-site.component.ts
import { Component } from '@angular/core';

import { ProductInfo } from './../product/product.component'

@Component({
  selector: 'app-ec-site',
  templateUrl: './ec-site.component.html',
  styleUrls: ['./ec-site.component.css']
})
export class EcSiteComponent {

  products: ProductInfo[] = [
    { id: 0, name: 'ＴシャツＡ', image: 'data:image/png;base64,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' },
    { id: 1, name: 'ＴシャツＢ', image: 'data:image/png;base64,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' },
    { id: 2, name: 'ＴシャツＣ', image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAHBElEQVR4Xu2dX3LjRBDGe+zKvhJOQMQF2JyA7Ak2VFmufSN7AuAEhBMQTkDytmW7CucEm5wA5wLI3CC8OhsP1Ro5dhL/7ZHkmdanF6qIe9z9fT+NWvJo1hCORitgGl09iicA0HAIAAAAaLgCDS8fMwAAaLgCDS8fMwAAaLgCDS8fMwAAaLgCDS8fMwAACFeB7PT0kN68eU9Ep0R0l/R65+FmO88s63Y5z++IaEiTyXUyHN6HmndwM0D24cMRPT6+J2NOCuOddtaOkn7/OFQhF/PK0vRvMubt0/+z9iaHod2+Tj59GodUQxAAZGnKYn1PRGfPhHut1JisvQhRyBzc6ZRr4LP/aKXJ1o7ImCFZe530+6N9w7A3AHLTjfmxOMtXC7ZaIYZhTMbc0HTKot5Tu/1v1WdYflk6OODpnYpZ6i1Ze7QB3FVV8GzAMFztC4ZaAcg6HZ7a+Xp+SsYcVko/n2lE82svA0K07Rl3SNbOp3CXKANbXc7Wcn4MwzAZDK4r1WZh8EoBeNbEWXviIeBd0VTVpUsZ3yPPmWHgma2GJrJ0ABauhe5M9zystf+Zh4cje3AwNsZ85TlcLeF5zkQnluimpJyHOQyt1m3Zl7hSAHjq3Dc3cbsbYO1t0u+fFN9xScZwoxXuYe0ttdtnbFSWpjel5+subZdlNcJiAEpo4rYz0XXLTzNJlqZnxd1CWCCw8cZcJL0en635UQkAz1XzbiJ3AuCpiXP36JLOfTvTn3/qt2UPgHIAXV/BDeV+YGDTrR3y9XpZF188EPpVUrQghu+KbnZtItcC8NTEsdB1dO7Lq75Kej0+69cexYzEUDIY3K3nXbwlOjLGfLMpfsXf78h153y4W03+r7XjbW7bsjRlOPhJZr3Hwh0Ffflyu+5J5CsAXjx+9W7ivCsvegDvcfYwQA2XgG2rck3kksfSOQCVNnHbprj6c+Ok10v8h6l/hKzbzWq8VG5X4Ism0rx6br3dMPV+ajL5OuQfVJaJUdwOMwDhHtaOTNbt2nAzLDKz9mPS718Gn+dCgvndijF/hp5zLABwl/0udDEX88vS9HPxW0HQaccBAEto7fE2nXcIamedzgm1Wp9DyGVTDjEBEM0sEMvZz3DEA4BD+YfFJ22b6N7H37Nul2+d/9rHd0u+My4A+AFHu31c9g8iEuFWdv6Pj7waqLqfjctKthgnLgBcLzCih4d3od0WFgtFuPF7uY6gZMvKHS4+AAKEIFbzY+wB5vgHMhPEbH7cADgUxjSdfkwGA149U/tR3O7xw566fhktvcY4LwGvZTinyeSPuvqC4gezn4oVwKWbUueAWgBwswHRedLrXVUpYNbt8krm9Uu/q0yg5LE1ATCThkG4pFbrqqzbxeKHHTae1yVEO90vY0cjAIuNoltZS8RvFd3ucvJkacqrjPiWbtPLKrsMG9xndQPwUm73iha/0LG8aXQrn2YvfARnVhUJNQuAKhSMfEwAELmBvukDAF8FI48HAJEb6Js+APBVMPJ4ABC5gb7pAwBfBSOPBwCRG+ibPgDwVTDyeAAQuYG+6QMAXwUjjwcAkRvomz4A8FUw8ngAELmBvukDAF8FI48HAJEb6Js+APBVMPJ4ABC5gb7pAwBfBSOPBwCRG+ibfpkAzLdUmy265O3a3J6CbndtHLsqwJrybqNuq7r5olV++7gUTdcCUOx5O9the7ZPHtF0Oltde7/Nrh1Zp/MztVq/71p9oz8/nf6SDAYXmzQoNsx0r6O3Wm5V8/N9Et+u26+YAeANDXj9+4im05y0Kt61q3nXzE26hf33Lc3ftYj8XUYHymwjzdFOW8Xu+oUvP/9Pmt6XtHu2byrBxvOs+22/X9sGE7UCENDOmcECQDXvjFovAO5f06pr8+RwTV6f2dLNsasqBgBUpax8XAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CL0AyDVBZFUK1PovhlRVBMaVKwAA5NqpiAQAKmyUFwEA5NqpiAQAKmyUFwEA5NqpiAQAKmyUFwEA5NqpiAQAKmyUFwEA5NqpiAQAKmyUF/E/lhlG06YKCNIAAAAASUVORK5CYII=' },
    { id: 3, name: 'ボトムスＡ', image: 'data:image/png;base64,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' },
    { id: 4, name: 'ボトムスＢ', image: 'data:image/png;base64,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' },
  ];

  constructor() { }

  onAddProduct(product: ProductInfo) {
    alert(`「${product.name}」を買い物かごへ追加`);
  }
}
```

```ec-site.component.html
<div class="products">
  <app-product *ngFor="let product of products" [product]="product"></app-product>
</div>
```

```ec-site.component.css
.products {
    display: flex;
    flex-wrap: wrap;
}
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158277/d8cf1aec-5a86-bbdb-9387-1f39f481f896.png)


## コンポーネントにコンテンツを表示

コンポーネント内に `<ng-content>` を利用することで、呼び出し元で指定したコンテンツを表示することができます。

```product.component.html
<div class="product">
  <img [src]="product?.image">
  <p>{{product?.name}}</p>
  <ng-content></ng-content>
</div>
```

```ec-site.component.html
<div class="products">
  <app-product *ngFor="let product of products" [product]="product">
    <p>セール中！</p>
  </app-product>
</div>
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158277/93566bf2-77e9-c89f-65f8-d82d9d272d1d.png)

`<ng-content>` に `select="セレクタ"` を付けることで、複数のコンテンツを表示することもできます。

```product.component.html
<div class="product">
  <img [src]="product?.image">
  <p>{{product?.name}}</p>
  <ng-content select=".sale"></ng-content>
  <ng-content select="div"></ng-content>
</div>
```

```ec-site.component.html
<div class="products">
  <app-product *ngFor="let product of products" [product]="product">
    <p class="sale">セール中！</p>
    <div>お知らせ</div>
  </app-product>
</div>
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158277/9749ef77-4c76-4d1c-9713-70f6ced5551f.png)

## コンポーネントのイベントを処理する

コンポーネント内で発生したイベントを呼び出し元のコンポーネントへ伝えるには `Output` と `EventEmitter` を使用します。

`@Output() プロパティ名 = new EventEmitter<型>();` で定義して、`emit(オブジェクト)` でイベントを伝えます。

```product.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

export class ProductInfo {
  id: number;
  name: string;
  image?: string;
}

@Component({
  selector: 'app-product',
  templateUrl: './product.component.html',
  styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {

  @Input('product') product: ProductInfo;

  @Output() onAddProduct = new EventEmitter<ProductInfo>();

  constructor() { }

  ngOnInit() {
  }

  onAddClick() {
    this.onAddProduct.emit(this.product);
  }
}
```

```product.component.html
<div class="product">
  <img [src]="product?.image">
  <p>{{product?.name}}</p>
  <button (click)="onAddClick()">追加</button>
</div>
```

```ec-site.component.html
<div class="products">
  <app-product *ngFor="let product of products" [product]="product" (onAddProduct)="onAddProduct($event)">
  </app-product>
</div>
```

```ec-site.component.ts
import { Component, OnInit } from '@angular/core';

import { ProductInfo } from './../product/product.component'

@Component({
  selector: 'app-component',
  templateUrl: './ec-site.component.html',
  styleUrls: ['./ec-site.component.css']
})
export class EcSiteComponent implements OnInit {

  products: ProductInfo[] = [
    { id: 0, name: 'ＴシャツＡ', image: 'data:image/png;base64,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' },
    { id: 1, name: 'ＴシャツＢ', image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAHdElEQVR4Xu2dTVLjVhDHu2VDZWYT5wQhy1QqNuywN3h2qQwuyAlgTpDkBCEnCDnBwAliykwqO8wGs8N2KpXlODcwG5ICW516MgYD/pCe1bL6ubVFr9X9///01PrwA0G3pVYAl7p6LR4UgCWHQAFQAJZcgSUvX2cABWDJFVjy8nUGUACWXIElL19nAAVgyRVY8vJ1BlAA0qvA7tV67s77ZAcRd4GwdVq4OEhvto+ZbbdKB4BUIKLqiv/fSXWj2U1r3qmbAb75e3Mte4s7gFBGwN2hcETQPC1cbKRVyNG8tlulK0RYf8gdqA4E1d4qnfzx5WUnTTWkAoBv/yqtez3aAsD9UeFeCEXUIYDDNAppwM3cwpYHeACIa5NMNiCDB1Xfg5Pfv7poLhqGhQFgTM/0aQ8IdqcJNlEgAwNCBxDraEQF6t6t0D/cZ5i5LPUzrwomL0IsA9E6EK5NBXcyDR1AqPYzeLwoGBIFoNIu7QDA7sB0yHHSH5xpSKPX3i4ghjrjkChH8DiFmzyRcJ01Z4KugQEAqrX8xQmnNqOxWQEYbeKQsGwrIAG0ECA466Rsc+VM0CWkehJNZOwADK+FpnMfbeKsjSO6zvj/rvW9V2a6/9Q6TpIDia77WSxnelSPI2cCqhoY+qtwHvclLhYAHjr3WU2clQl0Xss3yuYYK3d4BIBbVmESG0Tndyu0b4yqtIv1uPMNLm1AR3E1wtYAzN3EhTfkpJa/eLgdrLQ3983dQtzChk9nYkd3DgCHtXzDXMeDjQOAJ0cnmruJjATAYxNHZavO3UJlIvh53AMgAyD2qOwh7C4OBjr3CaqUxfq4Lt48EEKEnyzKjj4kgAHrUZvIqQAMmzgPvXISnfv4qum4lm/sz1LkfkZaI9OtA+WGt2VEsIaIn88aP+7vg0ZucCcxmHqxi0jNfgY7YW7bKu2SmQ3MnU+y28gdRaZ/cz7tSeQLAJ507iNP4pKtYPRogx5gcce3PzL7JSBkasMmctxj6QAA3iYuZJYTL63UqRUaX8wZZSHDK63ix6QulWELfN5E4vPn1mEDJblfpn/zWZpfqIzTYnDX4n1MUqeoxzIwYKVdoqgDk9/ff1fLXx4lf1z7Iw7uVrz39hGSGSkCAAKqn+Ybb5KRJJ6jbLeLZwiY+t5FBADGkn4GNsJ03vHYN1+U7T83y0je2XxRkhktBgBJs4CUs98gJgaAwflA340+aUvmHIl2lEq7aB5M/RZt1OL2lgUAQfdu1d+I+4VIXPIHnf+td2X71jOuPKLEkQXA/RO5rH/zJm23heYBWs97fWb1YUgUx2LeVxwAwYWAoJkmCKSaL7AHeMQ/LRBINl80AIOekDrk0bvTry/NW7DEt+B2z8f3aXvcG0UIkZeA5wUSwkG2d/NrUn1BcNZnX3+PBCJ+pzANCCcAGM4GPtDBh8LlcZQzIOq+b1ube7M+/Y4ac5H7uwPAUMXgsoBHvax/HNftYvC2tOftoU/7kqf7caC5B8BIlebpoflFDiI0a/mG+WQr9FZpF7eIzKfhM36sEjpiOnd0GoAXvYIBImh9g0+nXm5EwcsbCS9x4sJpqQCISzSX4igALrlpUYsCYCGaS0MUAJfctKhFAbAQzaUhCoBLblrUogBYiObSEAXAJTctalEALERzaYgC4JKbFrUoABaiuTREAXDJTYtaFAAL0VwaogC45KZFLQqAhWguDVEAXHLTohYFwEI0l4YoAC65aVGLAmAhmktDFACX3LSoJTYAni6pNvzoknKAWJa2zq+FjixDjKZA5kNWDJaqQxx8tEqAubg0nQ4A0TWgWR/vcZ28oFLPD76q9T2vG2bVjret4g8e4i8sKjka1Cf68UOhcTirvOB/Lfj+YOV137tfkuZxnUQwn7ZPWWMZzYIGZnFFswAi3S+vzvFbu0RXzZylWsr/Htb8qGWY3zIGMwlhbuh5pKViox7w+f6VVtGs2S9jxe95i7UdT3RdKzRY/5fCaGrJAsCweratzukdl+zKqIkCoJeB2dhNWhx79ki7PRQAO93YRikAbNLKCKwAyPCJLUsFgE1aGYEVABk+sWWpALBJKyOwAiDDJ7YsFQA2aWUEVgBk+MSWpQLAJq2MwAqADJ/YslQA2KSVEVgBkOETW5YKAJu0MgIrADJ8YstSAWCTVkZgBUCGT2xZKgBs0soIrADI8IktSwWATVoZgRUAGT6xZakAsEkrI7ACIMMntiwVADZpZQRWAGT4xJalAsAmrYzACoAMn9iyVADYpJURWAGQ4RNblgoAm7QyAisAMnxiy1IBYJNWRmAFQIZPbFkqAGzSygisAMjwiS1LBYBNWhmBFQAZPrFlqQCwSSsjsAIgwye2LBUANmllBFYAZPjElqUCwCatjMAKgAyf2LJUANiklRFYAZDhE1uWCgCbtDICKwAyfGLLUgFgk1ZGYAVAhk9sWToNAJtqGthagUT/Y4h1ljqQTQEFgE1aGYEVABk+sWWpALBJKyOwAiDDJ7YsFQA2aWUEVgBk+MSWpQLAJq2MwAqADJ/YslQA2KSVEVgBkOETW5b/A03J9DQKf6dTAAAAAElFTkSuQmCC' },
    { id: 2, name: 'ＴシャツＣ', image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAHBElEQVR4Xu2dX3LjRBDGe+zKvhJOQMQF2JyA7Ak2VFmufSN7AuAEhBMQTkDytmW7CucEm5wA5wLI3CC8OhsP1Ro5dhL/7ZHkmdanF6qIe9z9fT+NWvJo1hCORitgGl09iicA0HAIAAAAaLgCDS8fMwAAaLgCDS8fMwAAaLgCDS8fMwAAaLgCDS8fMwAACFeB7PT0kN68eU9Ep0R0l/R65+FmO88s63Y5z++IaEiTyXUyHN6HmndwM0D24cMRPT6+J2NOCuOddtaOkn7/OFQhF/PK0vRvMubt0/+z9iaHod2+Tj59GodUQxAAZGnKYn1PRGfPhHut1JisvQhRyBzc6ZRr4LP/aKXJ1o7ImCFZe530+6N9w7A3AHLTjfmxOMtXC7ZaIYZhTMbc0HTKot5Tu/1v1WdYflk6OODpnYpZ6i1Ze7QB3FVV8GzAMFztC4ZaAcg6HZ7a+Xp+SsYcVko/n2lE82svA0K07Rl3SNbOp3CXKANbXc7Wcn4MwzAZDK4r1WZh8EoBeNbEWXviIeBd0VTVpUsZ3yPPmWHgma2GJrJ0ABauhe5M9zystf+Zh4cje3AwNsZ85TlcLeF5zkQnluimpJyHOQyt1m3Zl7hSAHjq3Dc3cbsbYO1t0u+fFN9xScZwoxXuYe0ttdtnbFSWpjel5+subZdlNcJiAEpo4rYz0XXLTzNJlqZnxd1CWCCw8cZcJL0en635UQkAz1XzbiJ3AuCpiXP36JLOfTvTn3/qt2UPgHIAXV/BDeV+YGDTrR3y9XpZF188EPpVUrQghu+KbnZtItcC8NTEsdB1dO7Lq75Kej0+69cexYzEUDIY3K3nXbwlOjLGfLMpfsXf78h153y4W03+r7XjbW7bsjRlOPhJZr3Hwh0Ffflyu+5J5CsAXjx+9W7ivCsvegDvcfYwQA2XgG2rck3kksfSOQCVNnHbprj6c+Ok10v8h6l/hKzbzWq8VG5X4Ism0rx6br3dMPV+ajL5OuQfVJaJUdwOMwDhHtaOTNbt2nAzLDKz9mPS718Gn+dCgvndijF/hp5zLABwl/0udDEX88vS9HPxW0HQaccBAEto7fE2nXcIamedzgm1Wp9DyGVTDjEBEM0sEMvZz3DEA4BD+YfFJ22b6N7H37Nul2+d/9rHd0u+My4A+AFHu31c9g8iEuFWdv6Pj7waqLqfjctKthgnLgBcLzCih4d3od0WFgtFuPF7uY6gZMvKHS4+AAKEIFbzY+wB5vgHMhPEbH7cADgUxjSdfkwGA149U/tR3O7xw566fhktvcY4LwGvZTinyeSPuvqC4gezn4oVwKWbUueAWgBwswHRedLrXVUpYNbt8krm9Uu/q0yg5LE1ATCThkG4pFbrqqzbxeKHHTae1yVEO90vY0cjAIuNoltZS8RvFd3ucvJkacqrjPiWbtPLKrsMG9xndQPwUm73iha/0LG8aXQrn2YvfARnVhUJNQuAKhSMfEwAELmBvukDAF8FI48HAJEb6Js+APBVMPJ4ABC5gb7pAwBfBSOPBwCRG+ibPgDwVTDyeAAQuYG+6QMAXwUjjwcAkRvomz4A8FUw8ngAELmBvukDAF8FI48HAJEb6Js+APBVMPJ4ABC5gb7pAwBfBSOPBwCRG+ibfpkAzLdUmy265O3a3J6CbndtHLsqwJrybqNuq7r5olV++7gUTdcCUOx5O9the7ZPHtF0Oltde7/Nrh1Zp/MztVq/71p9oz8/nf6SDAYXmzQoNsx0r6O3Wm5V8/N9Et+u26+YAeANDXj9+4im05y0Kt61q3nXzE26hf33Lc3ftYj8XUYHymwjzdFOW8Xu+oUvP/9Pmt6XtHu2byrBxvOs+22/X9sGE7UCENDOmcECQDXvjFovAO5f06pr8+RwTV6f2dLNsasqBgBUpax8XAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CL0AyDVBZFUK1PovhlRVBMaVKwAA5NqpiAQAKmyUFwEA5NqpiAQAKmyUFwEA5NqpiAQAKmyUFwEA5NqpiAQAKmyUFwEA5NqpiAQAKmyUF/E/lhlG06YKCNIAAAAASUVORK5CYII=' },
    { id: 3, name: 'ボトムスＡ', image: 'data:image/png;base64,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' },
    { id: 4, name: 'ボトムスＢ', image: 'data:image/png;base64,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' },
  ];

  constructor() { }

  ngOnInit() {
  }

  onAddProduct(product: ProductInfo) {
    alert(`「${product.name}」を買い物かごへ追加`);
  }
}
```

## ライフサイクル

Angularには、コンポーネントの生成時や、状態の変更時など、特定のタイミングで呼ばれる**ライフサイクルメソッド**が用意されています。

| メソッド名             | 実行タイミング              |
|:----------------------|:------------------|
| ngOnChanges           | コンポーネントの入力プロパティ(`@Input`)変更時 |
| ngOnInit              | コンポーネントの初期化時 |
| ngDoCheck             | 変更を検知したとき |
| ngAfterContentInit    | 外部コンテンツ初期化時 |
| ngAfterContentChecked | 外部コンテンツ変更時 |
| ngAfterViewInit       | 自分自身と子コンポーネントのビュー初期化時 |
| ngAfterViewChecked    | 自分自身と子コンポーネントのビュー変更時 |
| ngOnDestroy           | コンポーネントが破棄されるとき |

詳細は以下で説明されています。
https://qiita.com/ksh-fthr/items/ccd9861f919c4aa30ae8

# サービス

共通のビジネスロジックを提供する仕組みをサービスといいます。
コンポーネントやディレクティブの `constructor` にサービス型の引数を指定することで、依存性注入（Dependency Injection、DI とも呼ばれます）され、使うことができます。

アクセス修飾子を指定しない場合は constructor内のみ、`private` を指定した場合はクラス内、`public` を指定した場合はクラス外へ公開されます。

```bash
$ ng generate service study2/product/product
```

```product.service.ts
import { Injectable } from '@angular/core';

import { ProductInfo } from './product.component'

@Injectable({
  providedIn: 'root'
})
export class ProductService {

  private _products: ProductInfo[] = [
    { id: 0, name: 'ＴシャツＡ', image: 'data:image/png;base64,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' },
    { id: 1, name: 'ＴシャツＢ', image: 'data:image/png;base64,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' },
    { id: 2, name: 'ＴシャツＣ', image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAHBElEQVR4Xu2dX3LjRBDGe+zKvhJOQMQF2JyA7Ak2VFmufSN7AuAEhBMQTkDytmW7CucEm5wA5wLI3CC8OhsP1Ro5dhL/7ZHkmdanF6qIe9z9fT+NWvJo1hCORitgGl09iicA0HAIAAAAaLgCDS8fMwAAaLgCDS8fMwAAaLgCDS8fMwAAaLgCDS8fMwAACFeB7PT0kN68eU9Ep0R0l/R65+FmO88s63Y5z++IaEiTyXUyHN6HmndwM0D24cMRPT6+J2NOCuOddtaOkn7/OFQhF/PK0vRvMubt0/+z9iaHod2+Tj59GodUQxAAZGnKYn1PRGfPhHut1JisvQhRyBzc6ZRr4LP/aKXJ1o7ImCFZe530+6N9w7A3AHLTjfmxOMtXC7ZaIYZhTMbc0HTKot5Tu/1v1WdYflk6OODpnYpZ6i1Ze7QB3FVV8GzAMFztC4ZaAcg6HZ7a+Xp+SsYcVko/n2lE82svA0K07Rl3SNbOp3CXKANbXc7Wcn4MwzAZDK4r1WZh8EoBeNbEWXviIeBd0VTVpUsZ3yPPmWHgma2GJrJ0ABauhe5M9zystf+Zh4cje3AwNsZ85TlcLeF5zkQnluimpJyHOQyt1m3Zl7hSAHjq3Dc3cbsbYO1t0u+fFN9xScZwoxXuYe0ttdtnbFSWpjel5+subZdlNcJiAEpo4rYz0XXLTzNJlqZnxd1CWCCw8cZcJL0en635UQkAz1XzbiJ3AuCpiXP36JLOfTvTn3/qt2UPgHIAXV/BDeV+YGDTrR3y9XpZF188EPpVUrQghu+KbnZtItcC8NTEsdB1dO7Lq75Kej0+69cexYzEUDIY3K3nXbwlOjLGfLMpfsXf78h153y4W03+r7XjbW7bsjRlOPhJZr3Hwh0Ffflyu+5J5CsAXjx+9W7ivCsvegDvcfYwQA2XgG2rck3kksfSOQCVNnHbprj6c+Ok10v8h6l/hKzbzWq8VG5X4Ism0rx6br3dMPV+ajL5OuQfVJaJUdwOMwDhHtaOTNbt2nAzLDKz9mPS718Gn+dCgvndijF/hp5zLABwl/0udDEX88vS9HPxW0HQaccBAEto7fE2nXcIamedzgm1Wp9DyGVTDjEBEM0sEMvZz3DEA4BD+YfFJ22b6N7H37Nul2+d/9rHd0u+My4A+AFHu31c9g8iEuFWdv6Pj7waqLqfjctKthgnLgBcLzCih4d3od0WFgtFuPF7uY6gZMvKHS4+AAKEIFbzY+wB5vgHMhPEbH7cADgUxjSdfkwGA149U/tR3O7xw566fhktvcY4LwGvZTinyeSPuvqC4gezn4oVwKWbUueAWgBwswHRedLrXVUpYNbt8krm9Uu/q0yg5LE1ATCThkG4pFbrqqzbxeKHHTae1yVEO90vY0cjAIuNoltZS8RvFd3ucvJkacqrjPiWbtPLKrsMG9xndQPwUm73iha/0LG8aXQrn2YvfARnVhUJNQuAKhSMfEwAELmBvukDAF8FI48HAJEb6Js+APBVMPJ4ABC5gb7pAwBfBSOPBwCRG+ibPgDwVTDyeAAQuYG+6QMAXwUjjwcAkRvomz4A8FUw8ngAELmBvukDAF8FI48HAJEb6Js+APBVMPJ4ABC5gb7pAwBfBSOPBwCRG+ibfpkAzLdUmy265O3a3J6CbndtHLsqwJrybqNuq7r5olV++7gUTdcCUOx5O9the7ZPHtF0Oltde7/Nrh1Zp/MztVq/71p9oz8/nf6SDAYXmzQoNsx0r6O3Wm5V8/N9Et+u26+YAeANDXj9+4im05y0Kt61q3nXzE26hf33Lc3ftYj8XUYHymwjzdFOW8Xu+oUvP/9Pmt6XtHu2byrBxvOs+22/X9sGE7UCENDOmcECQDXvjFovAO5f06pr8+RwTV6f2dLNsasqBgBUpax8XAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CAAg105FJABQYaO8CL0AyDVBZFUK1PovhlRVBMaVKwAA5NqpiAQAKmyUFwEA5NqpiAQAKmyUFwEA5NqpiAQAKmyUFwEA5NqpiAQAKmyUFwEA5NqpiAQAKmyUF/E/lhlG06YKCNIAAAAASUVORK5CYII=' },
    { id: 3, name: 'ボトムスＡ', image: 'data:image/png;base64,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' },
    { id: 4, name: 'ボトムスＢ', image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAIrElEQVR4Xu2da6hUVRTH1zpzlSKhIspSsntniIjeJBGIGD1J75ySsAcRRQT2ACVE5xiYBtXMGSUiK6iIKCIqgmqOF3tR5gcjohf0+uA2oqKwkCg/ZXd23HtTZ7znsdfyrDE2637U//qvvf/7x94zKkuEAfzUwtgOoM1kC9NpYG+vWj3eBgiLWP0tfGiSxiX7a6thvAEB1rO8GEWH7oVhUVjSF1ahmilQAHjBKQCM3PQGoIWmN0BeXvoE0GjKUusTwMtRnwBGbvoE0ELTJ0CfABoxHLU+AZzUpn+l5bnkV+kNoDeABFf9nnoD8DL25kMgb/taNYgEBvIEDGIj2oOXgALAy82bqoEAUKu3XgHE671JbQAbsWDHdnWiUelWgwJgNyCeKL0Zn/wtwO5dncZs6T0pANIJM/0VAGZwvpQpAL6cJHMfCgAzOF/KFABfTpK5DwWAGZwvZQqALyfJ3IcCwAzOlzIFwJeTZO5DAWAG50uZAuDLSTL3oQAwg/OlTAHw5SSZ+1AAmMH5UqYA+HKSzH0oAMzgfClTAHw5SeY+FABmcL6UKQC+nCRzHwoAMzhfyhQAX06SuQ8FgBmcL2UKgC8nydyHAsAMzpcyBcCXk2TuQwFgBudLmQLgy0ky96EAMIPzpUwB8OUkmftQAJjB+VKmAPhyksx9KADM4HwpUwB8OUnmPhQAZnC+lCkAvpwkcx8KADM4X8oUAF9OkrkPBYAZnC9lCoAvJ8nchwLADM69zO61Fs76p2uHZlTwWwCc6V4rr1QApDO28JlJGhdOtKmFrc8A8ALplhR/BYCSFk/7luk0rp4CIH4LAK7i2chUKQAyufa42hdMJ7p1EoB6/AIg3CLektBAASCExZJau8kk0er/ANgECKtYPkJFCoBQsAdsu7DGbGls/O8zwBoAjKVbUvwVAEpaPO1tptN4fuoGaN4GGDzHs5GpUgBkcj3oau1ik0RbJ36hOtpejIEdk25J8VcAKGlxtOPd+WZs7aeTAITxfAT4hGMjVaMASCW73xeH5pk3V/04CUC9NQ8Rf5BuSfFXAChpMbTm5+NmwqfL902WXvjUjNrcP/5m2IiVKABi0QKAtX+aJDq2t0UtbP0FgLMk21K8FQBKWmSt3Wk60el9ANRbBhCrZCuhAgVAKNgpW7vDdKIF/QDEHwHCxaJtCeYKACEsstTCGyZpLO1/AuI3ASAkewkVKABCwU5dAPC0SRrLD7kBngGEOyTbUrwVAEpaZK190HSidYd8BngIEO8jWwkVKABCwU7dAHaFSaLNvS2qYWslAj4q2ZbirQBQ0iJr7U2mE73cB0C9fROifYlsJVSgAAgFO3UDdC8zydr3+wC4pnk52uBdybYUbwWAkhZVOx6cY8ZWf9VbNhJuPDeA7pdUKym9AiCV7IRvJZhtXl+9u7fF8OL2yZUh+4tkW4q3AkBJi6C1FuyuZE0FAG1/mcVqvT2OCAP5D7WLlqwAFCXE/X1rfzNJdFJaeS1s/Q6AJ3Cty6xTAMpMs9fL2q9NEp2dCkA9/gYQzpRqTfFVAChp0bQfmE7j0gwAtgHCIpqdjFoBkMl14o+BXzFJ48b0JyB+FQCWSbWm+CoAlLQoWms3myRakVZSrcdPIMLdFDsprQIglSzYdaYTPZgKQBivR4ANYq0JxgoAISyaFJebzpqn0wFo3YWAT9L8ZNQKgEyuE58Blpqk8Ub6E9C8DjF4Tao1xVcBoKRF0Y7bBWYs2pFWMrKkvTCo2O0UOymtAiCVLHRPN521O1MBCOMzAoDvxFoTjBUAQlgUKe47+tidW1f8mVYzb0nz+BmVYA/FT0qrAMgku890GrmTQKr1uPt/+PsABUAGgJ9Mp3FqnnUtjH8GgDky7d1dFQD3rNyVPWNhsoqq9dYXiHieu6mMUgGQyfXAWJhsAOJ3EOEKmfburgqAe1YE5cGxMJkAhPGLCHAzwVREqgBIxNozFibnCXgEEe+VaE/xVAAoablqe8bC5AAQIWLT1VJKpwDIJHtgLEz2E9C8HSF4Vqa9u6sC4J6Vu7JnLEz2DdAeRbSJu6mMUgGQyLVnLEyW/chofFEQwMcS7SmeCgAlLVdtz1iYrJLha5vDlW7wvaullE4BEEi2byxMhv/sKzceM+uo7l6B9iRLBYAUl4M4ZSxMVlUtjCfmBc1wcBWTKAClRzt9LEz2N4HW9wg4XPoSCIYKACEsN+n0sTDZ3wTijxHhIjdfGZUCUHauKWNhcgBIEGG07CVQ/BQASlou2pSxMDlPwLMIeLuLrZRGASg92eljYbJvgFYTEaPSl0AwVAAIYTlJu7DSbGk85qKt1tv3ItpHXLRSGgWg9GSnj4XJfgLimxHgxdKXQDBUAAhhOUlTxsJk1Y2MNq8MguBtJ18hkQJQdrApY2GyWgwvbp9fGbKfl70Eip8CQEnLRZsyFiar7NQwnjMTYOIfhx6xHwWgxOizx8JkNbFYC9vdEpdAtlIAyJHlFOSMhcn8IFhv7UHE48tcBsVLAaCkVaTNGQuT803gOwQ4o8ha6vcVgHKTzRwLkwPAdgRYWO4y3N0UAPesipU5Y2FynoDXEPG6YnMZhQJQZq45Y2Gyb4DWkwh4V5nLoHgpAJS0CrXZY2FyboAHEPH+QmshgQJQarDZY2Gy2oyMtu4JAny81GUQzBQAQliF0pyxMJkAhO1lAdiJsXFH5EcBKDP2nLEwWW2G6+1FFbTbylwGxUsBoKRVqM0eC5NVetqS+MyhCnxTaC0kUABKDDZvLExWm7lLHz7hqPHK7yUug2SlAJDiyhUXjoVJrz6y4+MVgPIAKBwLk/1VMP4FEU4ubynuTgqAe1b5SoexMDkAfIkI55a1FIqPVwBU661fEXE2JYAStYVjYXIAeA8RLitxLc5W1sKvu5LGKc4FTOFA/nuUahivB4B1CFBhrpNfZu2dJome4hiMhPGqAGATp/awa6x91STRDYftU2DwL7ZAGMwHd21mAAAAAElFTkSuQmCC' },
  ];

  constructor() { }

  products() {
    return this._products;
  }
}
```

```ec-site.component.ts
import { Component } from '@angular/core';

import { ProductInfo } from './../product/product.component'
import { ProductService } from '../product/product.service';

@Component({
  selector: 'app-ec-site',
  templateUrl: './ec-site.component.html',
  styleUrls: ['./ec-site.component.css']
})
export class EcSiteComponent {

  constructor(public productService: ProductService) { }

  onAddProduct(product: ProductInfo) {
    alert(`「${product.name}」を買い物かごへ追加`);
  }
}
```

```ec-site.component.html
<div class="products">
  <app-product *ngFor="let product of productService.products()" [product]="product" (onAddProduct)="onAddProduct($event)">
  </app-product>
</div>
```

## インスタンス生成方法

サービスは、モジュールやコンポーネントの `providers` プロパティでインスタンスの生成方法を指定することができます。

デフォルトで `useClass` が指定され、`providedIn` で `root` を指定した場合、または app.module で指定した場合は、アプリケーション全体で１つのインスタンス（シングルトン）となります。

| 指定方法      | 説明 |
|:-------------|:------------------|
| useClass     | クラスを指定して、Injectされるたびにそのクラスのインスタンスを生成。 |
| useValue     | オブジェクトを指定して、Injectされるたびにそのオブジェクトを参照。 |
| useExisting  | DIトークン(サービス)を指定して、エイリアス(別名)で生成。 |
| useFactory   | ファクトリー関数を指定して、Injectの際にオブジェクトを生成。 |

# HTTP通信

Angular の HTTP通信は `@angular/common/http` の `HttpClientModule` で提供されています。
`app.module.ts` に `HttpClientModule` のインポートを追加してください。

> Angular 4.2 までのHTTP通信は `@angular/http` の `HttpModule` を利用していましたが、Angular 4.3 からは改良された `@angular/common/http` の `HttpClientModule` が追加され、これまでの `HttpModule` は非推奨となりました。

```app.module.ts
import { HttpClientModule } from '@angular/common/http';

:

@NgModule({
  declarations: [
      :
  ],
  imports: [
      :
    HttpClientModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```


新にコンポーネントを生成し、`http.component.ts` で `HttpClient` をインポートし、constructor で DI (Dependency Injection)してください。
`this.http.get(url, option)` で HTTP の GETリクエストが送信でき、`.subscribe()` で結果を処理できます。
`get<型>()` で、レスポンスの型を指定できます。（指定しない場合は any となる）
`HttpClient` には `get` の他に `post`, `put`, `delete` などのメソッドが用意されています。
https://angular.io/api/common/http/HttpClient

```bash
$ ng generate component study2/http
```

```http.component.ts
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

class ResponseBody {
  resultCount: number;
  results: Array<any>;
}

@Component({
  selector: 'app-http',
  templateUrl: './http.component.html',
  styleUrls: ['./http.component.css']
})
export class HttpComponent implements OnInit {

  url = 'https://itunes.apple.com/lookup?id=292706922';
  response: ResponseBody;
  error: any;

  // HttpClient を DI する
  constructor(private http: HttpClient) { }

  ngOnInit() {
    // HTTP GETリクエスト
    this.http.get<ResponseBody>(this.url).subscribe(
      // 成功時のコールバック
      response => this.response = response,
      // 失敗時のコールバック
      error => this.error = error
    );
  }
}
```

```http.component.html
<p>{{url}}</p>
<pre *ngIf="response">
  {{response | json}}
</pre>
<pre *ngIf="error">
  {{error | json}}
</pre>
```

```:実行結果
https://itunes.apple.com/lookup?id=292706922

  {
  "resultCount": 1,
  "results": [
    {
      "wrapperType": "artist",
      "artistType": "Artist",
      "artistName": "AKB48",
      "artistLinkUrl": "https://itunes.apple.com/us/artist/akb48/292706922?uo=4",
      "artistId": 292706922,
      "amgArtistId": 989429,
      "primaryGenreName": "J-Pop",
      "primaryGenreId": 27
    }
  ]
}
```

レスポンスのフォーマットはデフォルトでは JSON ですが、オプションの `responseType` で指定することもできます。

```ts
this.http.get(this.url, { responseType: 'text' })
```

レスポンスの内容はデフォルトでは body だけですが、オプションの `observe` に `response` を指定することで、ヘッダーやステータスコード等を含むレスポンスを取得できます。

```http.component.ts
import { Component, OnInit } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';

class ResponseBody {
  resultCount: number;
  results: Array<any>;
}

@Component({
  selector: 'app-http',
  templateUrl: './http.component.html',
  styleUrls: ['./http.component.css']
})
export class HttpComponent implements OnInit {

  url = 'https://itunes.apple.com/lookup?id=292706922';
  response: HttpResponse<ResponseBody>;
  error: any;

  // HttpClient を DI する
  constructor(private http: HttpClient) { }

  ngOnInit() {
    // HTTP GETリクエスト
    this.http.get<ResponseBody>(this.url, { observe: 'response' }).subscribe(
      // 成功時のコールバック
      response => this.response = response,
      // 失敗時のコールバック
      error => this.error = error
    );
  }
}
```

```:実行結果
https://itunes.apple.com/lookup?id=292706922

  {
  "headers": {
    "normalizedNames": {},
    "lazyUpdate": null
  },
  "status": 200,
  "statusText": "OK",
  "url": "https://itunes.apple.com/lookup?id=292706922",
  "ok": true,
  "type": 4,
  "body": {
    "resultCount": 1,
    "results": [
      {
        "wrapperType": "artist",
        "artistType": "Artist",
        "artistName": "AKB48",
        "artistLinkUrl": "https://itunes.apple.com/us/artist/akb48/292706922?uo=4",
        "artistId": 292706922,
        "amgArtistId": 989429,
        "primaryGenreName": "J-Pop",
        "primaryGenreId": 27
      }
    ]
  }
}
```

# 楽曲検索アプリ作成

## Step1. 楽曲情報を Component化

[前回の復習](#前回の復習)で作成したページの楽曲情報を Component で作成してください。

```bash
$ ng generate component study2/music-search/music-item
```

<details><summary>答え</summary><div>

```music-item.component.html
<div>
  <img [src]="item?.artworkUrl100">
  <div class="track-name">{{item?.trackName}}</div>
  <div class="artist-name">{{item?.artistName}}</div>
</div>
```

```music-item.component.css
.track-name {
    font-weight: bold;
}
```

```music-item.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { MusicItem } from '../music-search.service';

@Component({
  selector: 'app-music-item',
  templateUrl: './music-item.component.html',
  styleUrls: ['./music-item.component.css']
})
export class MusicItemComponent implements OnInit {

  @Input() item: MusicItem;

  constructor() { }

  ngOnInit() {
  }

}
```

```music-search.component.html
<div>
  <input type="text">
  <button (click)="onSearch()">検索</button>
  <div id="resultContent">
    <app-music-item *ngFor="let item of results" [item]="item"></app-music-item>
  </div>
</div>
```

</div></details>


## Step2. データ取得処理を Service化

データ取得処理を Service で作成してください。

```bash
$ ng generate service study2/music-search/music-search
```

<details><summary>答え</summary><div>

```music-search.service.ts
import { Injectable } from '@angular/core';

export class MusicItem {
  trackName: string;
  artistName: string;
  artworkUrl100: string;
}

@Injectable({
  providedIn: 'root'
})
export class MusicSearchService {

  constructor() { }

  search() {
    return [
      { artistName: 'アーティスト名１', trackName: '楽曲名１', artworkUrl100: 'https://is5-ssl.mzstatic.com/image/thumb/Music6/v4/a5/df/97/a5df97ec-b7e4-7f78-625a-f331603b0756/source/100x100bb.jpg' },
      { artistName: 'アーティスト名２', trackName: '楽曲名２', artworkUrl100: 'https://is4-ssl.mzstatic.com/image/thumb/Music/v4/fb/d8/04/fbd8047c-293e-1f90-4b06-929bbeca20b1/source/100x100bb.jpg' },
      { artistName: 'アーティスト名３', trackName: '楽曲名３', artworkUrl100: 'https://is2-ssl.mzstatic.com/image/thumb/Music/v4/4d/2e/39/4d2e39ef-fd24-a902-e704-cb7dc2ae6c5b/source/100x100bb.jpg' },
      { artistName: 'アーティスト名４', trackName: '楽曲名４', artworkUrl100: 'https://is5-ssl.mzstatic.com/image/thumb/Music/v4/54/ff/d6/54ffd6b2-3f3f-ce92-189d-20c3143b2380/source/100x100bb.jpg' },
      { artistName: 'アーティスト名５', trackName: '楽曲名５', artworkUrl100: 'https://is2-ssl.mzstatic.com/image/thumb/Music6/v4/ae/4a/fd/ae4afdd5-0dc7-31a4-ab96-eacc806a32d5/source/100x100bb.jpg' }
    ];
  }
}
```

```music-search.component.ts
import { Component, OnInit } from '@angular/core';
import { MusicSearchService, MusicItem } from './music-search.service';

@Component({
  selector: 'app-music-search',
  templateUrl: './music-search.component.html',
  styleUrls: ['./music-search.component.css']
})
export class MusicSearchComponent implements OnInit {

  constructor(private musicSearch: MusicSearchService) { }

  /** 検索結果 */
  results: MusicItem[];

  ngOnInit() {
  }

  /**
   * 検索実行
   */
  onSearch() {
    this.results = this.musicSearch.search();
  }
}
```
</div></details>


## Step3. APIで取得した結果を表示

iTunes Search API を呼び出し、テキストボックスに入力したキーワードのアーティストの楽曲を検索し取得結果を表示してください。

![step3](https://qiita-image-store.s3.amazonaws.com/0/158277/098c93ee-3d3f-f19a-e735-7fb69edb5848.png)

<details><summary>答え</summary><div>

```music-search.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

export class ResponseBody {
  resultCount: number;
  results: MusicItem[];
}

export class MusicItem {
  trackName: string;
  artistName: string;
  artworkUrl100: string;
}

@Injectable({
  providedIn: 'root'
})
export class MusicSearchService {

  constructor(private http: HttpClient) { }

  search(keyword: string) {
    var url = `https://itunes.apple.com/search?term=${keyword}&country=jp&media=music&attribute=artistTerm`;
    return this.http.get<ResponseBody>(url);
  }
}
```

```music-search.component.html
<div>
  <input type="text" #keyword>
  <button (click)="onSearch(keyword.value)">検索</button>
  <div id="resultContent">
    <app-music-item *ngFor="let item of results" [item]="item"></app-music-item>
  </div>
</div>
```

```music-search.component.ts
import { Component, OnInit } from '@angular/core';
import { MusicSearchService, MusicItem } from './music-search.service';

@Component({
  selector: 'app-music-search',
  templateUrl: './music-search.component.html',
  styleUrls: ['./music-search.component.css']
})
export class MusicSearchComponent implements OnInit {

  constructor(private musicSearch: MusicSearchService) { }

  /** 検索結果 */
  results: MusicItem[];

  ngOnInit() {
  }

  /**
   * 検索実行
   * @param keyword 検索キーワード
   */
  onSearch(keyword: string) {
    this.musicSearch.search(keyword).subscribe(response => {
      this.results = response.results;
    });
  }
}
```
</div></details>

## Step4. 検索対象の切り替え

アーティスト名、曲名のラジオボタンを追加して、選択した値を対象として検索するようにしてください。

![step4](https://qiita-image-store.s3.amazonaws.com/0/158277/479ec3b0-86d8-c4b8-3378-1a1ca245cbe9.png)

<details><summary>答え</summary><div>

```music-search.component.html
<div>
  <input type="text" #keyword>
  <button (click)="onSearch(keyword.value)">検索</button>
  <div>
    <input type="radio" name="attribute" value="artistTerm" [(ngModel)]="attribute">アーティスト名
    <input type="radio" name="attribute" value="songTerm" [(ngModel)]="attribute">曲名
  </div>
  <div id="resultContent">
    <app-music-item *ngFor="let item of results" [item]="item"></app-music-item>
  </div>
</div>
```

```music-search.component.ts
import { Component, OnInit } from '@angular/core';
import { MusicSearchService, MusicItem } from './music-search.service';

@Component({
  selector: 'app-music-search',
  templateUrl: './music-search.component.html',
  styleUrls: ['./music-search.component.css']
})
export class MusicSearchComponent implements OnInit {

  attribute = 'artistTerm';

  constructor(private musicSearch: MusicSearchService) { }

  /** 検索結果 */
  results: MusicItem[];

  ngOnInit() {
  }

  /**
   * 検索実行
   * @param keyword 検索キーワード
   */
  onSearch(keyword: string) {
    this.musicSearch.search(keyword, this.attribute).subscribe(response => {
      this.results = response.results;
    });
  }
}
```

```music-search.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

export class ResponseBody {
  resultCount: number;
  results: MusicItem[];
}

export class MusicItem {
  trackName: string;
  artistName: string;
  artworkUrl100: string;
}

@Injectable({
  providedIn: 'root'
})
export class MusicSearchService {

  constructor(private http: HttpClient) { }

  search(keyword: string, attribute: string) {
    var url = `https://itunes.apple.com/search?term=${keyword}&country=jp&media=music&attribute=${attribute}`;
    return this.http.get<ResponseBody>(url);
  }
}
```
</div></details>

## Step5. iTunes ページ表示

検索結果の楽曲をクリックしたときに、新しいウィンドウで iTunes の楽曲ページを表示してください。

![step5](https://qiita-image-store.s3.amazonaws.com/0/158277/da80d9d2-a0e0-c758-2a3c-94461f858d3f.gif)

<details><summary>答え</summary><div>

```music-search.component.ts
import { Component, OnInit } from '@angular/core';
import { MusicSearchService, MusicItem } from './music-search.service';

@Component({
  selector: 'app-music-search',
  templateUrl: './music-search.component.html',
  styleUrls: ['./music-search.component.css']
})
export class MusicSearchComponent implements OnInit {

  attribute = 'artistTerm';

  constructor(private musicSearch: MusicSearchService) { }

  /** 検索結果 */
  results: MusicItem[];

  ngOnInit() {
  }

  /**
   * 検索実行
   * @param keyword 検索キーワード
   */
  onSearch(keyword: string) {
    this.musicSearch.search(keyword, this.attribute).subscribe(response => {
      this.results = response.results;
    });
  }

  /**
   * 楽曲クリック時
   * @param item 楽曲情報
   */
  onItemClick(item: MusicItem) {
    // iTunesのページを新しいウィンドウで開く
    window.open(item.trackViewUrl, 'trackView', 'width=400, height=600');
  }
}
```
</div></details>

## Step6. 横並びレイアウト

検索結果を横に並べて表示してください。

![step6](https://qiita-image-store.s3.amazonaws.com/0/158277/70ee2b52-f87a-8275-7864-41a66c1bd69f.png)

<details><summary>答え</summary><div>

```music-search.component.css
#resultContent {
    display: flex;
    flex-wrap: wrap;
}

app-music-item {
    width: 200px;
    margin-bottom: 16px;
}
```
</div></details>

## Extra1. ページング

ページング機能を追加してください。

![ex1.gif](https://qiita-image-store.s3.amazonaws.com/0/158277/1fd5d6e2-6948-922a-6615-391d2f057c86.gif)

<details><summary>答え</summary><div>

```music-search.component.html
<div>
  <input type="text" [(ngModel)]="keyword">
  <button (click)="onSearch(0)">検索</button>
  <div>
    <input type="radio" name="attribute" value="artistTerm" [(ngModel)]="attribute">アーティスト名
    <input type="radio" name="attribute" value="songTerm" [(ngModel)]="attribute">曲名
  </div>
  <div id="resultContent">
    <app-music-item *ngFor="let item of results" [item]="item" (click)="onItemClick(item)"></app-music-item>
  </div>
  <div id="pager">
    <button (click)="onPrev()">←</button>
    <span id="pageIndex">{{currentPage + 1}}</span>
    <button (click)="onNext()">→</button>
  </div>
</div>
```

```music-search.component.css
#resultContent {
    display: flex;
    flex-wrap: wrap;
}

app-music-item {
    width: 200px;
    margin-bottom: 16px;
}

#resultContent {
    display: flex;
    flex-wrap: wrap;
}

#pager {
    display: flex;
    justify-content: space-around;
}
```

```music-search.component.ts
import { Component, OnInit } from '@angular/core';
import { MusicSearchService, MusicItem } from './music-search.service';

@Component({
  selector: 'app-music-search',
  templateUrl: './music-search.component.html',
  styleUrls: ['./music-search.component.css']
})
export class MusicSearchComponent implements OnInit {

  keyword: string;

  attribute = 'artistTerm';

  currentPage = 0;

  constructor(private musicSearch: MusicSearchService) { }

  /** 検索結果 */
  results: MusicItem[];

  ngOnInit() {
  }

  /**
   * 検索実行
   */
  onSearch(page = 0) {
    this.currentPage = page;
    this.musicSearch.search(this.keyword, this.attribute, page).subscribe(response => {
      this.results = response.results;
    });
  }

  /**
   * 楽曲クリック時
   * @param item 楽曲情報
   */
  onItemClick(item: MusicItem) {
    // iTunesのページを新しいウィンドウで開く
    window.open(item.trackViewUrl, 'trackView', 'width=400, height=600');
  }

  onPrev() {
    if (0 < this.currentPage) {
      this.onSearch(this.currentPage - 1);
    }
  }

  onNext() {
    this.onSearch(this.currentPage + 1);
  }
}
```

```music-search.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

export class ResponseBody {
  resultCount: number;
  results: MusicItem[];
}

export class MusicItem {
  trackName: string;
  artistName: string;
  artworkUrl100: string;
  trackViewUrl: string;
}

@Injectable({
  providedIn: 'root'
})
export class MusicSearchService {

  limit = 10;

  constructor(private http: HttpClient) { }

  search(keyword: string, attribute: string, page: number) {
    var url = `https://itunes.apple.com/search?term=${keyword}&country=jp&media=music&attribute=${attribute}&limit=${this.limit}&offset=${this.limit * page}`;
    return this.http.get<ResponseBody>(url);
  }
}
```
</div></details>