Help us understand the problem. What is going on with this article?

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インストール
nodej
推奨版を使用しています。

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
この規模のアプリであれば、コンポーネントは1つで作成可能ですが、あえてコンポーネント分割してサービスを使う構成にしています。angularのコンポーネント連携を簡単に体験できるようにという目的です。

"都道府県"表示コンポーネント

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

ng g component pref-view

コマンドによってsrc\app配下にperf-viewフォルダと4つのファイルが作成されます。
image.png

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

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
これから作成してゆく"市表示"コンポーネントも同様にapp.component.htmlファイルにタグで配置して行きます。

table作成

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

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

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フィルタを使用しています。

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指定や、ヘッダー背景色を設定します。

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
表示がうまくいかない場合や、空白ページが表示される場合はブラウザのコンソール画面を表示してエラーが起きていないか確認します。コンソール画面を表示するにはアプリケーション画面をアクティブにしたままF12キーを押します。
TypeScriptの構文ミスはコンパイルで検出されますが、テンプレート上の構文ミスの場合は何も表示されないケースが多いです。そんな時はブラウザのコンソール画面を確認して下さい。

行選択機能の追加

都道府県の行がクリックされた際に選択済みとする機能を追加します。そのために2つのファイルを編集します。

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

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タグ(データ表示行)に2つの属性を追加します。
click属性にイベントハンドラとして.tsで追加したonClickRowメソッドを指定します。引数に同タグ内で宣言している行データ変数のitemを指定しています。
style.background-color属性にはrowBackColorを指定し、同様にitemを引数に使用しています。

pref-view.component.html
<tr *ngFor="let item of data$" (click)="onClickRow(item)"
             [style.background-color]="rowBackColor(item)">

以上を編集保存して、コンパイルと画面更新が終了するのを待ちます。
成功した場合、行をクリックすることで該当行の背景色が変わります。
image.png
うまく行かない場合や、空白画面が表示される場合はブラウザのconsole画面を確認してみて下さい。

serviceの実装とDI

行選択の実装ができましたので「選択した内容を他のコンポーネントに通知し、データを受け渡す」仕組みの準備に入ります。まずはserviceを実装します。以下コマンドによりserviceのスケルトンファイルを作成します。

ng g service pref

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

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を宣言します。

pref-view.component.ts
import { Component, OnInit } from '@angular/core';
import {PrefService} from '../pref.service' //追加
pref-view.component.ts
  constructor(private service:PrefService) { } //引数追加

行選択された契機で、選択都道府県のコードを他のコンポーネントに通知します。これをするため、onClickRowメソッドでPrefServiceのsubject\$のメソッドを呼びます。

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フォルダと4つのファイルが作成されます。
image.png
.tsファイルはperf-viewと大きく変わりませんが、1点ポイントが有ります。

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で県が選択される都度呼ばれます。

city-view.component.ts
this.service.observe$.subscribe(
  (selectPrefCode)=>{           //コールバック関数
      this.selectPrefCode=selectPrefCode;  //
      this.setPrefCity();                  //
    }
);

.htmlファイルもperf-viewと構成的にほぼ同じです。.cssファイルはperf-viewと同じ内容なので記載を割愛します。

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に配置します。

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が正式リリースされました。materialを利用すればリッチな画面部品(component)が使用可能です。
これが何かコーディングのヒントになれば幸いです。それではまた。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away