サイトトップ

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

HTML5テクニカルノート

Angular 2入門 07: HTTPサービスでデータを追加・削除する


Angular 2入門 06: HTTPサービスでデータを取得・保存する」(以下「Angular 2入門 06」)でつくったサンプル「Angular 2 Example - Tour of Heroines: Part 6」は、データをHTTPサービスによりリモートで読み込んで表示し、書き替えて保存しました。さらに、新たなデータを加えたり、すでにあるデータも削除できるようにします。

01 HTTPサービスでデータを新たに加える

「Angular 2入門 06」で書いたコードに手を加えてゆきます。新たなデータは、リスト表示のモジュール(heroines.component)から加えることにしましょう。まず、テンプレートに、つぎのように「追加」の<button>要素を書き足します。クリック(clickイベント)で呼び出すコールバックは、この後コンポーネントに定めるメソッド(add())です。引数には、<input>要素(heroineName)に入力したテキストの値を渡します。

heroines.component.html

<div>
  <label>ヒロイン名:</label><input #heroineName />
  <button (click)="add(heroineName.value); heroineName.value=''">
    追加
  </button>
</div>

つぎに、リスト表示のモジュール(heroines.component)に定めるメソッド(add())は、つぎのように引数(name)に受け取ったテキストの要らない余白をString.trim()メソッドで取り除き、値のあることを確かめたうえで、データを扱うサービス(heroineServiceプロパティ)のメソッド(create())に渡します。そして、コールバックが受け取った値(heroine)を、データ(heroinesプロパティ)の配列に加えています。

heroines.component.ts


export class HeroinesComponent implements OnInit {

	add(name: string): void {
		name = name.trim();
		if (!name) {return;}
		this.heroineService.create(name)
			.then((heroine: Heroine) => {
				this.heroines.push(heroine);
				this.selectedHeroine = null;
		});
	}

}

データを扱うサービスのモジュール(heroine.service)に定めるのは、つぎのようなデータを新たにつくるメソッド(create())です。受け取った値(name)をJSON文字列にしてHttp.post()メソッドにリクエストとして渡し、Promiseオブジェクトのデータで返します。これで、テキスト入力フィールドに打ち込んだテキストが、新たなデータとして加えられるようになりました(図001)。手を加えたモジュールとテンプレートは、以下のコード001のとおりです。

heroine.service.ts


export class HeroineService {

	create(name: string): Promise<Heroine> {
		return this.http
			.post(this.heroinesUrl, JSON.stringify({name: name}), {headers: this.headers})
			.toPromise()
			.then((response: Response) => response.json().data)
			.catch(this.handleError);
	}

}

図001■テキスト入力フィールドから新たなデータが加えられる

図001

コード001■HTTPサービスで新たなデータを加える

heroines.component.html

<h2>ヒロインたち</h2>
<div>
	<label>ヒロイン名:</label> <input #heroineName />
	<button (click)="add(heroineName.value); heroineName.value=''">
		追加
	</button>
</div>
<ul class="heroines">
	<li *ngFor="let heroine of heroines"
			[class.selected]="heroine === selectedHeroine"
			(click)="onSelect(heroine)">
		<span class="badge">{{heroine.id}}</span>
		<span>{{heroine.name}}</span>
	</li>
</ul>
<div *ngIf="selectedHeroine">
	<h2>
		ヒロイン「{{selectedHeroine.name}}」
	</h2>
	<button (click)="gotoDetail()">詳細を見る</button>
</div>

heroines.component.ts

import {Component, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {Heroine} from './heroine';
import {HeroineService} from './heroine.service';
@Component({
	moduleId: module.id,
	selector: 'my-heroines',
	templateUrl: 'heroines.component.html',
	styleUrls: ['heroines.component.css']
})
export class HeroinesComponent implements OnInit {
	heroines: Heroine[];
	selectedHeroine: Heroine;
	constructor(
		private router: Router,
		private heroineService: HeroineService
	) {}
	onSelect(heroine: Heroine): void {
		this.selectedHeroine = heroine;
	}
	getHeroines(): void {
		this.heroineService.getHeroines().then((heroines: Heroine[]) => this.heroines = heroines);
	}
	add(name: string): void {
		name = name.trim();
		if (!name) {return;}
		this.heroineService.create(name)
			.then((heroine: Heroine) => {
				this.heroines.push(heroine);
				this.selectedHeroine = null;
		});
	}
	ngOnInit(): void {
		this.getHeroines();
	}
	gotoDetail(): void {
		this.router.navigate(['/detail', this.selectedHeroine.id]);
	}
}

heroine.service.ts

import {Injectable} from '@angular/core';
import {Headers, Http, Response} from '@angular/http';
import 'rxjs/add/operator/toPromise';
import {Heroine} from './heroine';
@Injectable()
export class HeroineService {
	private headers: Headers = new Headers({'Content-Type': 'application/json'});
	private heroinesUrl: string = 'api/heroines';
	constructor(private http: Http) {}
	getHeroines(): Promise<Heroine[]> {
		return this.http.get(this.heroinesUrl)
			.toPromise()
			.then((response: Response) => response.json().data as Heroine[])
			.catch(this.handleError);
	}
	getHeroine(id: number): Promise<Heroine> {
		const url: string = `${this.heroinesUrl}/${id}`;
		return this.http.get(url)
			.toPromise()
			.then((response: Response) => response.json().data as Heroine)
			.catch(this.handleError);
	}
	create(name: string): Promise<Heroine> {
		return this.http
			.post(this.heroinesUrl, JSON.stringify({name: name}), {headers: this.headers})
			.toPromise()
			.then((response: Response) => response.json().data)
			.catch(this.handleError);
	}
	update(heroine: Heroine): Promise<Heroine> {
		const url: string = `${this.heroinesUrl}/${heroine.id}`;
		return this.http
			.put(url, JSON.stringify(heroine), {headers: this.headers})
			.toPromise()
			.then(() => heroine)
			.catch(this.handleError);
	}
	private handleError(error: any): Promise<any> {
		console.error('An error occured', error);
		return Promise.reject(error.message || error);
	}
}

02 HTTPサービスでデータを除く

すでにリモートに加えられているデータを、HTTPサービスで取り除けるようにしましょう。リスト表示のモジュール(heroines.component)で、項目ごとに削除のボタンをつけることにします。テンプレート(heroines.component.html)の<li>要素に、つぎのように<button>(class属性"delete")を加えます。クリック(clickイベント)したらコンポーネントのメソッド(delete())をコールバックするのは、これまでと同じつくりです。引数には、削除すべきその項目のデータ(heroine)を渡します。さらに、Event.stopPropagation()メソッドを呼び出して、親の<li>要素にクリックが伝わるのを止めました。

heroines.component.html

<li *ngFor="let heroine of heroines"
		[class.selected]="heroine === selectedHeroine"
		(click)="onSelect(heroine)">

	<button class="delete"
		(click)="delete(heroine); $event.stopPropagation()">x</button>
</li>

リスト表示のモジュール(heroines.component)に定めるメソッド(delete())は、つぎのようにデータを扱うサービス(heroineServiceプロパティ)のメソッド(delete())に取り除くデータのidを渡します。リモートのデータはサービスにより除かれます。けれど、このコンポーネントのデータも改めなければなりません。そこで、削除するデータはArray.filter()メソッドで、プロパティ(heroines)の配列から取り去りました。また、その項目が選択されていた場合(selectedHeroine)は外します。

heroines.component.ts


export class HeroinesComponent implements OnInit {

	delete(heroine: Heroine): void {
		this.heroineService
			.delete(heroine.id)
			.then(() => {
				this.heroines = this.heroines.filter((_heroine: Heroine) => _heroine !== heroine);
				if (this.selectedHeroine === heroine) {
					this.selectedHeroine = null;
				}
			});
	}

}

データを扱うサービスのモジュール(heroine.service)に加える削除のメソッド(delete())は、受け取ったidから定めたURLをつぎのようにHttp.delete()メソッドに渡します。これで、HTTPサービスによりリモートのデータが除かれるのです。

heroine.service.ts


export class HeroineService {

	delete(id: number): Promise<void> {
		const url = `${this.heroinesUrl}/${id}`;
		return this.http.delete(url, {headers: this.headers})
			.toPromise()
			.then(() => null)
			.catch(this.handleError);
	}

}

リスト表示のモジュール(heroines.component)に加えた削除ボタンのスタイルも少し整えましょう。CSSファイル(heroines.component.css)に、つぎのクラス(delete)の定めを加えます。これでデータを加えるだけでなく、項目の削除ボタンで除くこともできるようになりました。手直ししたモジュールとテンプレートおよびCSSの定めは、つぎのコード002のとおりです。併せて、Plunkerに「Angular 2 Example - Tour of Heroines: Part 7」として、サンプルコードをアップロードしました。実際の動きや、すべてのモジュールおよびテンプレート、CSSのコードは、こちらでお確かめください。

heroines.component.css


button.delete {
  float:right;
  margin-top: 2px;
  margin-right: .8em;
  background-color: gray !important;
  color:white;
}

図002■削除のボタンでデータが除かれる

図002

コード002■HTTPサービスによりリモートのデータを追加・削除する

heroines.component.html

<h2>ヒロインたち</h2>
<div>
	<label>ヒロイン名:</label> <input #heroineName />
	<button (click)="add(heroineName.value); heroineName.value=''">
		追加
	</button>
</div>
<ul class="heroines">
	<li *ngFor="let heroine of heroines"
			[class.selected]="heroine === selectedHeroine"
			(click)="onSelect(heroine)">
		<span class="badge">{{heroine.id}}</span>
		<span>{{heroine.name}}</span>
		<button class="delete"
			(click)="delete(heroine); $event.stopPropagation()">x</button>
	</li>
</ul>
<div *ngIf="selectedHeroine">
	<h2>
		ヒロイン「{{selectedHeroine.name}}」
	</h2>
	<button (click)="gotoDetail()">詳細を見る</button>
</div>

heroines.component.ts

import {Component, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {Heroine} from './heroine';
import {HeroineService} from './heroine.service';
@Component({
	moduleId: module.id,
	selector: 'my-heroines',
	templateUrl: 'heroines.component.html',
	styleUrls: ['heroines.component.css']
})
export class HeroinesComponent implements OnInit {
	heroines: Heroine[];
	selectedHeroine: Heroine;
	constructor(
		private router: Router,
		private heroineService: HeroineService
	) {}
	onSelect(heroine: Heroine): void {
		this.selectedHeroine = heroine;
	}
	getHeroines(): void {
		this.heroineService.getHeroines().then((heroines: Heroine[]) => this.heroines = heroines);
	}
	add(name: string): void {
		name = name.trim();
		if (!name) {return;}
		this.heroineService.create(name)
			.then((heroine: Heroine) => {
				this.heroines.push(heroine);
				this.selectedHeroine = null;
		});
	}
	delete(heroine: Heroine): void {
		this.heroineService
			.delete(heroine.id)
			.then(() => {
				this.heroines = this.heroines.filter((_heroine: Heroine) => _heroine !== heroine);
				if (this.selectedHeroine === heroine) {
					this.selectedHeroine = null;
				}
			});
	}
	ngOnInit(): void {
		this.getHeroines();
	}
	gotoDetail(): void {
		this.router.navigate(['/detail', this.selectedHeroine.id]);
	}
}

heroines.component.css

.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;
}
button.delete {
  float:right;
  margin-top: 2px;
  margin-right: .8em;
  background-color: gray !important;
  color:white;
}

heroine.service.ts

import {Injectable} from '@angular/core';
import {Headers, Http, Response} from '@angular/http';
import 'rxjs/add/operator/toPromise';
import {Heroine} from './heroine';
@Injectable()
export class HeroineService {
	private headers: Headers = new Headers({'Content-Type': 'application/json'});
	private heroinesUrl: string = 'api/heroines';
	constructor(private http: Http) {}
	getHeroines(): Promise<Heroine[]> {
		return this.http.get(this.heroinesUrl)
			.toPromise()
			.then((response: Response) => response.json().data as Heroine[])
			.catch(this.handleError);
	}
	getHeroine(id: number): Promise<Heroine> {
		const url: string = `${this.heroinesUrl}/${id}`;
		return this.http.get(url)
			.toPromise()
			.then((response: Response) => response.json().data as Heroine)
			.catch(this.handleError);
	}
	create(name: string): Promise<Heroine> {
		return this.http
			.post(this.heroinesUrl, JSON.stringify({name: name}), {headers: this.headers})
			.toPromise()
			.then((response: Response) => response.json().data)
			.catch(this.handleError);
	}
	delete(id: number): Promise<void> {
		const url = `${this.heroinesUrl}/${id}`;
		return this.http.delete(url, {headers: this.headers})
			.toPromise()
			.then(() => null)
			.catch(this.handleError);
	}
	update(heroine: Heroine): Promise<Heroine> {
		const url: string = `${this.heroinesUrl}/${heroine.id}`;
		return this.http
			.put(url, JSON.stringify(heroine), {headers: this.headers})
			.toPromise()
			.then(() => heroine)
			.catch(this.handleError);
	}
	private handleError(error: any): Promise<any> {
		console.error('An error occured', error);
		return Promise.reject(error.message || error);
	}
}


作成者: 野中文雄
作成日: 2017年1月22日


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