サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

Angular 2入門 04: サービスをつくる


Angular 2入門 03: コンポーネントを分ける」(以下「Angular 2入門 03」)の作例(コード001)でデータを複数のコンポーネントから参照できるように、サービスという仕組みに切り分けます。コンポーネントはデータをもたずに済むので、それをどう表示するかだけに絞ってつくり込めます。コンポーネントごとの動作確認もしやすくなるでしょう。なお、本稿ででき上がる作例の動きそのものは、「Angular 2入門 03」コード001と変わりません。

01 データはモジュールに分けてサービスのモジュールを定める

前出「Angular 2入門 02」コード002では、コンポーネントのモジュール(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.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: 'フィオ' }
];

モジュール(mock-heroines.ts)に分けたデータを保持して、コンポーネントに提供するのが、新たにつくるつぎのようなサービスのモジュール(heroine.service)です。「app」フォルダにいれてください(図001)。他のコンポーネントから最新データのインスタンスが受け取れるように、importした関数(Injectable)でクラス(HeroineService)をデコレートします(「Dependency Injection」の「Angular dependency injection」を参照)。このようにサービスを分けると、データを用いるコンポーネントは、サービスがデータをどこからどのように得ているのか気にしなくて構いません。

heroine.service.ts

import {Injectable} from '@angular/core';
import {Heroine} from './heroine';
import {HEROINES} from './mock-heroines';
@Injectable()
export class HeroineService {
  getHeroines(): Heroine[] {
    return HEROINES;
  }
}

図001■アプリケーションのフォルダにデータとサービスのTypeScriptモジュールを加えた

図001

02 コンポーネントのモジュールを書き替える

つぎに、コンポーネント(app.component)のモジュールを書き替えます。以下のように、前項でつくったサービスのクラス(HeroineService)に加えて、もうひとつOnInitクラスimportします。そして、サービスのクラスは、コンポーネントのデコレータ関数(Component)に渡すオブジェクトのprovidersプロパティに配列で加えます。すると、コンポーネントのコンストラクタはソービスのインスタンスを引数に受け取りますので、constructor()を定めて引数値はprivateなプロパティ(heroineService)に与えました。

OnInitクラスのngOnInit()メソッドは、データバインディングが済んだときに、一度だけ呼び出されます。そのとき、サービス(heroineService)のメソッド(getHeroines())からデータを得て、プロパティ(heroines)に定めればよいでしょう。ここまで書き上げた3つのモジュールを、以下のコード001にまとめました(図002)。併せて、Plunkerに作例のコードを掲げてあります。

app.component.ts

import {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■リストの項目をクリックすると下にその情報が表れる

図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: 'フィオ' }
];

heroine.service.ts

import {Injectable} from '@angular/core';
import {Heroine} from './heroine';
import {HEROINES} from './mock-heroines';
@Injectable()
export class HeroineService {
  getHeroines(): Heroine[] {
    return HEROINES;
  }
}

app.component.ts

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>
		<my-heroine-detail [heroine]="selectedHeroine"></my-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: .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)と呼ばれ、抽象化されたデータ型を表します(「ジェネリクス」参照)。

heroine.service.ts


export class HeroineService {
	// getHeroines(): Heroine[] {
	getHeroines(): Promise<Heroine[]> {
		// return HEROINES;
		return Promise.resolve(HEROINES);
	}
}

Promiseオブジェクトが解決されると、Promise.then()メソッド メソッドが呼び出されます。第1引数に与える関数は、正しくデータが得られたときのコールバックです。そこで、コンポーネント(app.component)のデータを得るメソッド(getHeroines())は、つぎのように書き替えます。なお、TypeScriptのアロー関数式については、「TypeScript入門 09: アロー関数式」をご参照ください。

ビルドして確かめると、相変わらず「Angular 2入門 02: リストを加える」で書いたコードと動きは変わりません。けれど、非同期になりましたので、サーバーからデータを得るよう書き替えもできます。手を加えたサービス(heroine.service)とコンポーネント(app.component)のモジュールは、以下のコード002にまとめました。併せて、Plunkerに作例のコードを掲げておきます。

app.component.ts


export 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);
	}
}

app.component.ts

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>
		<my-heroine-detail [heroine]="selectedHeroine"></my-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: .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();
	}
}


作成者: 野中文雄
更新日: 2016年12月27日 アロー関数機の引数に型づけを追加。
作成日: 2016年12月10日


Copyright © 2001-2017 Fumio Nonaka.  All rights reserved.