angular サービスによるコンポーネント連携

#初めに
angularの提供するコンポーネントとサービスについて、使い勝手が良く気に入っています。コンポーネントは独立した部品として作成でき、サービスによってコンポーネント間のデータ渡しや同期を取ることでアプリケーションページとして構成することができます。コンポーネントの作成とサービスの作成・利用方法をハンズオン風に書いてみたいと思います。
内容は初心者向けです（私も初心者なので）。
#環境
Angular CLI: 7.0.6
Node: 8.11.4
OS: win32 x64(Windows 8.1)
Angular: 
Package                      Version
@angular-devkit/architect    0.10.6
@angular-devkit/core         7.0.6
@angular-devkit/schematics   7.0.6
@schematics/angular          7.0.6
@schematics/update           0.10.6
rxjs                         6.3.3
typescript                   3.1.6

エディタ:Visual Studio Code
ブラウザ:chrome

**node.jsインストール**
[nodejs](https://nodejs.org/ja/)
推奨版を使用しています。

**angular-cliインストール**
コマンドプロンプトを起動し、以下コマンドでインストールします。

```
npm install -g @angular/cli
```
**プロジェクトの作成**
コマンドプロンプトを起動し、任意のディレクトリに移動して以下コマンドを実行します。


```
ng new prefecture
```
angular-cliのver7から、router等の確認メッセージが表示されるようになりましたが、全てEnterで進めます。一連の処理が終わったら、確認のためにプロジェクトを起動します。

```
cd prefecture
ng serve --open
```
コンパイル終了後、ブラウザ上に"Welcome to prefector!"のメッセージとangularのロゴが表示されればプロジェクトの作成成功です。
同コマンド実行中は、コーディングの際にプロジェクト配下のファイル更新を自動検出してコンパイルしてくれます。さらにブラウザの表示も更新されるで、本コマンドは実行させたままとして下さい。
新規にコマンドプロンプトを開き、prefectureディレクトリ上で以下に記載するコマンドを実行して下さい。

#アプリの作成
親テーブル"都道府県"を選択したら、子テーブルの"市区町村"に該当のデータが表示される、簡単なアプリを作成します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94957/18f09f99-fb14-85c5-2a78-bf77a42d47bf.png)
この規模のアプリであれば、コンポーネントは１つで作成可能ですが、あえてコンポーネント分割してサービスを使う構成にしています。angularのコンポーネント連携を簡単に体験できるようにという目的です。

#"都道府県"表示コンポーネント
"都道府県"表示コンポーネント(perf-view)を作成するため以下コマンドを入力します。

```
ng g component pref-view
```
コマンドによってsrc\app配下にperf-viewフォルダと４つのファイルが作成されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94957/0e804eb5-3b7a-565b-59d9-10cc95fb65fe.png)




未だ何もコーディングしていませんが、この状態でpref-viewは４つのファイルのスケルトンによりコンポーネントとして構成されています。この初期状態のpref-viewをブラウザに表示してみることにします。src\app配下のapp.component.htmlファイルをエディタで開きます。書かれているコードを全て削除し、app-pref-viewタグを追加します。

```html:app.component.html
<app-pref-view></app-pref-view>

```
ファイルを保存してコンパイルの連動とブラウザ表示が更新されるので確認します。ブラウザを閉じてしまった場合は、以下URLで確認できます。

```
http://localhost:4200/
```
ブラウザにpref-view works!と表示されれば成功です。app-pref-viewタグはselectorというパラメータで、pref-view.component.tsファイルのComponentアノテーション内で指定されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94957/da587730-e6a0-1025-7348-878dbed2efdd.png)
これから作成してゆく"市表示"コンポーネントも同様にapp.component.htmlファイルにタグで配置して行きます。

##table作成
デフォルトデータを表示するtableを作成します。３つのファイルを編集します。

.tsファイルにはページ表示するデータの操作や、クリックイベントのハンドラなどページのバックグラウンドとなる処理を書きます。data\$はtable表示用にバインドされる変数として、public宣言しています。createData()はdata\$にデフォルトデータをセットします。コンポーネントの初期化メソッド(ngOnInit())経由で呼んでいます。

```typescript:pref-view.component.ts
import { Component, OnInit } from '@angular/core';

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

  public data$:any[];

  constructor() { }

  ngOnInit() {
    this.createData();
  }

  createData(){
    this.data$=[
      {prefCode:1,prefName:"北海道",prefArea:78419.54,prefPopulation:5285430},
      {prefCode:2,prefName:"青森県",prefArea:9645.65,prefPopulation:1262686},
      {prefCode:3,prefName:"秋田県",prefArea:11637.57,prefPopulation:980694},
      {prefCode:4,prefName:"岩手県",prefArea:15275.02,prefPopulation:1240522},
      {prefCode:5,prefName:"福島県",prefArea:13783.90,prefPopulation:1865143}
    ];
  }

}
```
.htmlファイルはangularのコンポーネント構成上はテンプレート(template)と称され、htmlの骨格を形成しながらngタグやバインド変数などを記載します。htmlのtableタグを配置し、タグの中でngForタグを使用してバインド変数を表示しています。数値の表示には整形のためにnumberフィルタを使用しています。

```html:pref-view.component.html
<table>
  <tr>
    <th>都道府県</th>
    <th>面積(km2)</th>
    <th>人口</th>
  </tr>
  <tr *ngFor="let item of data$">
    <td>{{item.prefName}}</td>
    <td class='td_num'>{{item.prefArea| number : '.2'}}</td>
    <td class='td_num'>{{item.prefPopulation| number}}</td>
  </tr>
</table>
```
.cssファイルの有効範囲はコンポーネント内に閉じています。
table内textのalign指定や、ヘッダー背景色を設定します。

```css:pref-view.component.css
table , td, th{
    border: 1px solid;
}

th{
    text-align: center;
    background-color:whitesmoke;
}

.td_num {
    text-align: right;    
}
```
ここまで編集してファイルを保存すると、ブラウザ上には以下tableが表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94957/58c95bab-820c-9449-0fa4-a0010f3fcf5f.png)
表示がうまくいかない場合や、空白ページが表示される場合はブラウザのコンソール画面を表示してエラーが起きていないか確認します。コンソール画面を表示するにはアプリケーション画面をアクティブにしたままF12キーを押します。
TypeScriptの構文ミスはコンパイルで検出されますが、テンプレート上の構文ミスの場合は何も表示されないケースが多いです。そんな時はブラウザのコンソール画面を確認して下さい。
##行選択機能の追加
都道府県の行がクリックされた際に選択済みとする機能を追加します。そのために２つのファイルを編集します。

.tsファイルではPrefViewComponentクラス内に１つの変数と２つのメソッドを追加します。
onClickRowメソッドは行クリックのイベントハンドラです。クリックされた行のデータを引数として渡され、変数selectItemに保持します。
rowBackColorは行の背景色を決めるメソッドです。該当行のデータを引数として渡されるので、prefCodeがselectItemのものと一致するか判定します。一致の場合は選択を示す色名を返し、不一致の場合は非選択を示す色名を返します。

```typescript:pref-view.component.ts
  private selectItem:any;

  onClickRow(item:any){
    this.selectItem=item;
  }

  rowBackColor(item:any):string{
    if(this.selectItem===undefined)return 'white';
    if(this.selectItem.prefCode===item.prefCode)return 'gainsboro';
    return 'white';
  }
```
.htmlではtrタグ(データ表示行)に２つの属性を追加します。
click属性にイベントハンドラとして.tsで追加したonClickRowメソッドを指定します。引数に同タグ内で宣言している行データ変数のitemを指定しています。
style.background-color属性にはrowBackColorを指定し、同様にitemを引数に使用しています。

```html:pref-view.component.html
<tr *ngFor="let item of data$" (click)="onClickRow(item)"
             [style.background-color]="rowBackColor(item)">
```
以上を編集保存して、コンパイルと画面更新が終了するのを待ちます。
成功した場合、行をクリックすることで該当行の背景色が変わります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94957/1434b94d-305a-0adc-48ba-60c8a1a336d0.png)
うまく行かない場合や、空白画面が表示される場合はブラウザのconsole画面を確認してみて下さい。
##serviceの実装とDI
行選択の実装ができましたので「選択した内容を他のコンポーネントに通知し、データを受け渡す」仕組みの準備に入ります。まずはserviceを実装します。以下コマンドによりserviceのスケルトンファイルを作成します。

```
ng g service pref
```
src/app配下にスケルトンファイルpref.service.tsが生成されるので、これを編集します。３行を追加します。
rxjsモジュール(非同期データ伝搬機能などを提供する)のimportと、データ書き込み用オブジェクト(subject\$)とデータ発生の通知と受け渡し用オブジェクト(observe\$)を実装します。

```typescript:pref.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs'; //追加

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

  public subject$ = new Subject<any>(); //追加
  public observe$ = this.subject$.asObservable();  //追加

  constructor() { }
  
}
```
pref-view.component.tsでは、まずPrefServiceの提供する機能を使用するため、PrefServiceをDI(Dependency Injection)します。PrefServiceのメソッドや変数をアクセスするための入り口を取得する訳ですが、特別難しいことは有りません。
PrefServiceをconstructで宣言します。そのためにimportを宣言します。

```typescript:pref-view.component.ts
import { Component, OnInit } from '@angular/core';
import {PrefService} from '../pref.service' //追加
```
```typescript:pref-view.component.ts
  constructor(private service:PrefService) { } //引数追加
```
行選択された契機で、選択都道府県のコードを他のコンポーネントに通知します。これをするため、onClickRowメソッドでPrefServiceのsubject\$のメソッドを呼びます。

```typescript:pref-view.component.ts
onClickRow(item:any){
    this.selectItem=item;
    this.service.subject$.next(item.prefCode); //追加
}
```
以上でperf-view.component側の連携準備は整いました。ファイルを保存し、コンパイル後にブラウザの表示とコンソールにエラーの無いことを確認して下さい。
#"市区町村"表示コンポーネント
"市区町村"表示コンポーネント(city-view)を作成するため以下コマンドを入力します。

```
ng g component city-view
```
コマンドによってsrc\app配下にperf-viewフォルダと４つのファイルが作成されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94957/2a255cc1-8c24-ed74-72cc-0248f2c3dcf5.png)
.tsファイルはperf-viewと大きく変わりませんが、１点ポイントが有ります。

```typescript:city-view.component.ts
import { Component, OnInit } from '@angular/core';
import {PrefService} from '../pref.service';

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

  public data$:any[];
  private baseData:any[];
  private selectPrefCode:any;

  constructor(private service:PrefService) { }

  ngOnInit() {

    this.createData();

    this.service.observe$.subscribe(
　　　(selectPrefCode)=>{
      　　this.selectPrefCode=selectPrefCode;
      　　this.setPrefCity();
    　}
　　);
  }

  setPrefCity(){
    this.data$=new Array();
    this.baseData.forEach(item=>{
      if(item.prefCode===this.selectPrefCode)this.data$.push(item);
    });
  }

  createData(){
    this.baseData=[
      {prefCode:1,cityCode:1,cityName:"札幌市",cityArea:1121.26,cityPopulation:1966416},
      {prefCode:1,cityCode:2,cityName:"釧路市",cityArea:1362.90,cityPopulation:168698},
      {prefCode:2,cityCode:1,cityName:"青森市",cityArea:824.61,cityPopulation:279133},
      {prefCode:2,cityCode:2,cityName:"八戸市",cityArea:305.56,cityPopulation:225463},
      {prefCode:3,cityCode:1,cityName:"秋田市",cityArea:906.07,cityPopulation:308482},
      {prefCode:3,cityCode:2,cityName:"横手市",cityArea:692.80,cityPopulation:87960},
      {prefCode:4,cityCode:1,cityName:"盛岡市",cityArea:886.47,cityPopulation:294047},
      {prefCode:4,cityCode:2,cityName:"一関市",cityArea:1256.42,cityPopulation:116479},
      {prefCode:5,cityCode:1,cityName:"いわき市",cityArea:1232.02,cityPopulation:342897},
      {prefCode:5,cityCode:2,cityName:"郡山市",cityArea:757.20,cityPopulation:332863}
    ];
  }

}

```
コンポーネント連動の仕組みとしてsubscribeメソッドを使用している点がポイントです。ngOnInit()の中で使用しており、subscribeの引数としてコールバック関数をセットしています。コールバック関数ではpref-viewで選択された県コードを受け取り、自コンポーネントの変数にセット、baseData変数から該当県の市データを抽出してdata\$変数にセットします。data\$変数はテンプレート(html)にバインドされた変数なので、これを契機に表示が自動で更新されます。コールバック関数はpref-viewで県が選択される都度呼ばれます。

```typescript:city-view.component.ts
this.service.observe$.subscribe(
　　(selectPrefCode)=>{　　　　　　　　　　　//コールバック関数
      this.selectPrefCode=selectPrefCode;  //
      this.setPrefCity();                  //
    }
);
```
.htmlファイルもperf-viewと構成的にほぼ同じです。.cssファイルはperf-viewと同じ内容なので記載を割愛します。

```html:city-view.component.html
<table>
  <tr>
    <th>市区町村</th>
    <th>面積(km2)</th>
    <th>人口</th>
  </tr>
  <tr *ngFor="let item of data$" >
    <td>{{item.cityName}}</td>
    <td class='td_num'>{{item.cityArea| number : '.2'}}</td>
    <td class='td_num'>{{item.cityPopulation| number}}</td>
  </tr>
</table>
```
以上作成したcity-view.componentをページに配置するため、同コンポーネントのselectorをタグとしてapp.component.htmlに配置します。

```html:app.component.html
<app-pref-view></app-pref-view>
<br>
<app-city-view></app-city-view>
```
作成したコンポーネントのselectorを書くだけで配置・再配置ができること、また部品のように使いまわしができることがangularの特徴です。

ファイルをすべて保存した後、ブラウザの表示を確認します。
都道府県テーブルを選択後、該当の市区町村データが表示されれば成功です。うまく表示されない場合は、コンソールを確認してみて下さい。

#最後に
実際のアプリケーションではDB連携など必要なので、そうした部分はRESTサービスを別途実装してWEB APIコールする方法を取ります。Node.jsにもExpressというhttpサービスモジュールが有ります。Express上でRESTサービスを構築すればTypeScriptでコードが統一できます。
UIに関してはangular  Ver6以降から対応の[material](https://material.angular.io/)が正式リリースされました。materialを利用すればリッチな画面部品(component)が使用可能です。
これが何かコーディングのヒントになれば幸いです。それではまた。