HTML5テクニカルノート
Angular 4入門 04: サービスをつくる
- ID: FN1706004
- Technique: HTML5 / JavaScript
- Package: Angular 4.0
「Angular 4入門 03: コンポーネントを分ける」(以下「Angular 4入門 03」)の作例(コード001)でデータを複数のコンポーネントから参照できるように、サービスという仕組みに切り分けます。コンポーネントはデータをもたずに済むので、それをどう表示するかだけに絞ってつくり込めます。コンポーネントごとの動作確認もしやすくなるでしょう。なお、本稿ででき上がる作例の動きそのものは、「Angular 4入門 03」コード001と変わりません。
Angular Version 5については「Angular 5入門 05: データをサービスにより提供する」をお読みください。
01 データはモジュールに分けてサービスのモジュールを定める
前出「Angular 4入門 03」コード001では、コンポーネントのモジュール(app.component)が、表示すべきデータをもっていました。このデータをつぎのように除いて、別のモジュール(mock-heroines)に移し、「app」フォルダに納めます。
app.component.ts/* const HEROINES: Heroine[] = [ {id: 11, name: 'シータ'}, {id: 12, name: 'ナウシカ'}, {id: 13, name: 'キキ'}, {id: 14, name: '千尋'}, {id: 15, name: 'さつき'}, {id: 16, name: 'ソフィー'}, {id: 17, name: 'マーニー'}, {id: 18, name: '菜穂子'}, {id: 19, name: 'サン'}, {id: 20, name: 'フィオ'} ]; */ export class AppComponent { }
mock-heroines.tsimport {Heroine} from './heroine'; export const HEROINES: Heroine[] = [ {id: 11, name: 'シータ'}, {id: 12, name: 'ナウシカ'}, {id: 13, name: 'キキ'}, {id: 14, name: '千尋'}, {id: 15, name: 'さつき'}, {id: 16, name: 'ソフィー'}, {id: 17, name: 'マーニー'}, {id: 18, name: '菜穂子'}, {id: 19, name: 'サン'}, {id: 20, name: 'フィオ'} ];
モジュール(mock-heroines.ts)に分けたデータを保持して、コンポーネントに提供するのが、新たにつくるつぎのサービスのモジュール(heroine.service)です。「app」フォルダに入れてください(図001)。他のコンポーネントが最新データのインスタンスを受け取れるように、クラス(HeroineService)はimport
した関数(Injectable()
)でデコレートします(「Dependency Injection」の「Angular dependency injection」を参照)。このようにサービスを分けると、データを用いるコンポーネントは、サービスがデータをどこからどのように得るのか気にせずにすむのです。
heroine.service.tsimport {Injectable} from '@angular/core'; import {Heroine} from './heroine'; import {HEROINES} from './mock-heroines'; @Injectable() export class HeroineService { getHeroines(): Heroine[] { return HEROINES; } }
図001■アプリケーションのフォルダにデータとサービスのTypeScriptモジュールを加えた
02 コンポーネントのモジュールを書き替える
つぎに、コンポーネント(app.component)のモジュールを書き替えます。以下のように、前項でつくったサービスのクラス(HeroineService)に加えて、もうひとつOnInit
クラスをimport
します。そして、サービスのクラスは、コンポーネントのデコレータ関数(Component
)に渡すオブジェクトのproviders
プロパティに配列で加えます。すると、コンポーネントのコンストラクタはサービスのインスタンスを引数に受け取りますので、constructor()
を定めて引数値はprivate
なプロパティ(heroineService)に与えました。
OnInit
クラスのngOnInit()
メソッドは、データバインディングが済んだときに、一度だけ呼び出されます。そのとき、サービス(heroineService)のメソッド(getHeroines())からデータを得て、プロパティ(heroines)に定めればよいでしょう。ここまで書き上げた3つのモジュールを、以下のコード001にまとめました(図002)。併せて、Plunkerに作例のコードを掲げてあります。
app.component.tsimport {Component, OnInit} from '@angular/core'; import {HeroineService} from './heroine.service'; @Component({ providers: [HeroineService] }) export class AppComponent { heroines: Heroine[]; // = HEROINES; constructor(private heroineService: HeroineService) {} getHeroines(): void { this.heroines = this.heroineService.getHeroines(); } ngOnInit(): void { this.getHeroines(); } }
図002■リストの項目をクリックすると下にその情報が表れる
コード001■データとサービスおよびコンポーネントのモジュール
mock-heroines.ts
import {Heroine} from './heroine';
export const HEROINES: Heroine[] = [
{id: 11, name: 'シータ'},
{id: 12, name: 'ナウシカ'},
{id: 13, name: 'キキ'},
{id: 14, name: '千尋'},
{id: 15, name: 'さつき'},
{id: 16, name: 'ソフィー'},
{id: 17, name: 'マーニー'},
{id: 18, name: '菜穂子'},
{id: 19, name: 'サン'},
{id: 20, name: 'フィオ'}
];
import {Injectable} from '@angular/core';
import {Heroine} from './heroine';
import {HEROINES} from './mock-heroines';
@Injectable()
export class HeroineService {
getHeroines(): Heroine[] {
return HEROINES;
}
}
import {Component, OnInit} from '@angular/core';
import {Heroine} from './heroine';
import {HeroineService} from './heroine.service';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<h2>ヒロインたち</h2>
<ul class="heroines">
<li *ngFor="let heroine of heroines"
[class.selected]="heroine === selectedHeroine"
(click)="onSelect(heroine)">
<span class="badge">{{heroine.id}}</span> {{heroine.name}}
</li>
</ul>
<heroine-detail [heroine]="selectedHeroine"></heroine-detail>
`,
styles: [`
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroines {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroines li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: 0.5em;
padding: 0.3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroines li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroines li:hover {
color: #607D8B;
background-color: #DDD;
left: 0.1em;
}
.heroines .text {
position: relative;
top: -3px;
}
.heroines .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: 0.8em;
border-radius: 4px 0 0 4px;
}
`],
providers: [HeroineService]
})
export class AppComponent {
title = 'ヒロイン一覧';
heroines: Heroine[];
selectedHeroine: Heroine;
constructor(private heroineService: HeroineService) {}
onSelect(heroine: Heroine): void {
this.selectedHeroine = heroine;
}
getHeroines(): void {
this.heroines = this.heroineService.getHeroines();
}
ngOnInit(): void {
this.getHeroines();
}
}
03 データを非同期で受け取れるようにする
前掲コード001のサービスのモジュール(heroine.service)で、データ(HEROINES)を返すクラス(HeroineService)のメソッド(getHeroines())は、同期処理でデータが直ちに得られる前提です。HTTPからデータを受け取ろうとすれば、非同期にしなければなりません。その場合には、Promise
クラスを使います。つぎのように静的メソッドPromise.resolve()
にデータの参照を渡せば、Promise
オブジェクトが返され、非同期でデータを待つことができるのです。なお、山かっこ<>
を用いた型づけは「ジェネリック」(Generics)と呼ばれ、抽象化されたデータ型を表します(「TypeScript: ジェネリック型」参照)。
heroine.service.tsexport class HeroineService { // getHeroines(): Heroine[] { getHeroines(): Promise<Heroine[]> { // return HEROINES; return Promise.resolve(HEROINES); } }
Promise
オブジェクトが解決されると、Promise.then()
メソッド
メソッドが呼び出されます。第1引数に与える関数は、正しくデータが得られたときのコールバックです。そこで、コンポーネント(app.component)のデータを得るメソッド(getHeroines())は、つぎのように書き替えます。なお、TypeScriptのアロー関数式については、「TypeScript入門 09: アロー関数式」をご参照ください。
ビルドして確かめると、相変わらず「Angular 4入門 03」コード001と動きは変わりません。けれど、非同期になりましたので、サーバーからデータを得るよう書き替えもできるのです。手を加えたサービス(heroine.service)とコンポーネント(app.component)のモジュールは、以下のコード002にまとめました。併せて、Plunkerに作例のコードを掲げておきます。
app.component.tsexport class AppComponent { getHeroines(): void { // this.heroines = this.heroineService.getHeroines(); this.heroineService.getHeroines().then((heroines: Heroine[]) => this.heroines = heroines); } }
コード002■非同期の処理に改めたサービスとコンポーネントのモジュール
heroine.service.ts
import {Injectable} from '@angular/core';
import {Heroine} from './heroine';
import {HEROINES} from './mock-heroines';
@Injectable()
export class HeroineService {
getHeroines(): Promise<Heroine[]> {
return Promise.resolve(HEROINES);
}
}
import {Component, OnInit} from '@angular/core';
import {Heroine} from './heroine';
import {HeroineService} from './heroine.service';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<h2>ヒロインたち</h2>
<ul class="heroines">
<li *ngFor="let heroine of heroines"
[class.selected]="heroine === selectedHeroine"
(click)="onSelect(heroine)">
<span class="badge">{{heroine.id}}</span> {{heroine.name}}
</li>
</ul>
<heroine-detail [heroine]="selectedHeroine"></heroine-detail>
`,
styles: [`
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroines {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroines li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: 0.5em;
padding: 0.3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroines li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroines li:hover {
color: #607D8B;
background-color: #DDD;
left: 0.1em;
}
.heroines .text {
position: relative;
top: -3px;
}
.heroines .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: 0.8em;
border-radius: 4px 0 0 4px;
}
`],
providers: [HeroineService]
})
export class AppComponent {
title = 'ヒロイン一覧';
heroines: Heroine[];
selectedHeroine: Heroine;
constructor(private heroineService: HeroineService) {}
onSelect(heroine: Heroine): void {
this.selectedHeroine = heroine;
}
getHeroines(): void {
this.heroineService.getHeroines().then((heroines: Heroine[]) => this.heroines = heroines);
}
ngOnInit(): void {
this.getHeroines();
}
}
- Angular 4: とにかくAngular 4でコードを書いて動かす
- Angular 4入門 01: 編集ページをつくる
- Angular 4入門 02: リストを加える
- Angular 4入門 03: コンポーネントを分ける
- Angular 4入門 05: リスト表示のコンポーネントを分ける
- Angular 4入門 06: Routerを使う
- Angular 4入門 07: Routerで画面を遷移させる
- Angular 4入門 08: HTTPサービスでデータを取得・保存する
- Angular 4入門 09: HTTPサービスでデータを追加・削除する
作成者: 野中文雄
作成日: 2017年6月5日
Copyright © 2001-2017 Fumio Nonaka. All rights reserved.