はじめに
前記事で「ナビゲーションの追加」を通して、主にAngularのルーティングについて理解を深めた。続いて「データの管理」に進む。当セクションは非常にボリュームがあるため2記事に分けて進めていく。
今回はAngularの構成単位であるService、それに伴い依存性の注入(DI)についての理解を深めていく。前回に引き続きルーティングの設定も行うため復習にもなると思う。
ショッピングカートサービスを作成する
これまでのチュートリアルで今現在、商品の共有(shareボタン)、通知(Notify Meボタン)、そして商品の詳細ページヘの遷移、詳細の閲覧ができる。
今回はECサイトでよく見る、商品をカートへ追加する機能を構築する。具体的にはBuyボタンを追加し、カート内の情報を保存するためのカートサービスを設定する。
カートサービスを定義する
cart service
を生成する。今回はComponentではなくServiceを作成するので、コマンドのgenerateの後はservice
とする
ng generate service cart
【Angular】公式チュートリアルの理解1 (入門)で、Serviceとはビジネスロジック等の関数を実行するためのファイルだと説明した。
そのためCartService
というクラスにメソッドを下記のように追加していく。
import { Product } from './products';
/* . . . */
export class CartService {
items: Product[] = [];
/* . . . */
addToCart(product: Product) {
this.items.push(product);
}
getItems() {
return this.items;
}
clearCart() {
this.items = [];
return this.items;
}
/* . . . */
}
まず、Productインターフェイスをimportする。と書いてあるが、下記にも抜粋したproducts.tsファイルを見てほしい。これはJavaScriptの仕様でもあるが、interfaceのProduct
とconstのproducts
の2つがexportされている。つまり、この2つを外部で利用可能という設定がexportでなされている。
実際に外部で使用する際にはこれらを指定する。これまではproducts.tsの中のproductsがimportされていたが、今回はProductがimportされている。
以上からProductはinterfaceなので上記のCartServiceではProduct型の配列であるitemsという名前の配列を定義し、初期値を空としている。
export interface Product {
..........
}
export const products = [
{..........
その他のメソッドは単に定義されているのみで、特に問題は無いと思う。メソッド内容は書かれているので確認しながら理解するといいと思うが、これまでのComponentでは行われてこなかったビジネスロジックの関数(メソッド)が定義されている。
カートサービスを使用する
ここでは上記で定義したCartServiceを使用するための設定を行う。コードの説明はコメント文で追記しておく。
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Product, products } from '../products';
//この部分でcart.service.tsファイルを当該ファイルで使用できるようにする。
import { CartService } from '../cart.service';
続いてDIの設定を行う。
export class ProductDetailsComponent implements OnInit {
constructor(
private route: ActivatedRoute,
//この部分:constructorの引数でインスタンス化することでCartSeriviceクラスを使えるようにしている。
//前回の「ActivatedRoute」と同様な設定をしているだけなのでわからなくなったら前回を確認
private cartService: CartService
) { }
}
続いて、web上でBuy
ボタンをクリックすると商品をカートに追加する仕様を設計する。(constructorは消さずにその下にaddToCartメソッドを追記)。
実際に行うことは書いてあるが、Product型の変数を引数とするメソッドで、引数のproductをカートに追加し、アラートを表示するものである。
export class ProductDetailsComponent implements OnInit {
addToCart(product: Product) {
this.cartService.addToCart(product);
window.alert('Your product has been added to the cart!');
}
}
このメソッドをクリックと結びつける設定を以下で行う。
初回で説明したイベントバインディングと同じ記述方法である。
<h2>Product Details</h2>
<div *ngIf="product">
<h3>{{ product.name }}</h3>
<h4>{{ product.price | currency }}</h4>
<p>{{ product.description }}</p>
<!-- この部分 -->
<button type="button" (click)="addToCart(product)">Buy</button>
</div>
アプリ上でBuyボタンをクリックしてアラートが表示されればここはOKである。
カートビューを作成する
上記でBuyボタンでカートに追加したカート内容を見るためにカートビューを作成する。
2段階で作成するとのことだが、前回と同様な設定を行う。簡単に言うと、リンクであるルーティング設定→画面表示設定の順に行う。
カートコンポーネントを設定する
今回はComponent
を作成する。
ng generate component cart
ドキュメントにあるように、selectorの設定やhtml,cssファイルなどの紐付けをコピペ
する。ngOnInit()
は無視できる、と書いてあるが、これはつまりCartComponentを生成した際に行う処理が今回、特にないので消しても問題ないということである。(コピペは必ず行う)
※これはCartComponent以下のimplements OnInitを消した書き方になっていることに繋がる。implements以下を書く場合、OnInitを継承したngOnInit()メソッドを記述する必要があるが、implements以下がない場合、ngOnInit()メソッドがなくてもエラーにはならない。つまりこれはjavaのインターフェイスの考え方と同様である。
ドキュメント通り、ng generate~~でcomponentを作成したことによってapp.moduleのdeclarationsに自動で追記されることを確認する。
続いて、ルーティングの設定を行う。前回のProductDetaisComponentと同様な設定なのでこの2つの例をみてルーティングの設定が何かが理解できると良い。
コメントで記入したので理解しながら進めてもらいたい。
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule,
RouterModule.forRoot([
{ path: '', component: ProductListComponent },
{ path: 'products/:productId', component: ProductDetailsComponent },
//この部分でpathとcomponentを紐付ける
{ path: 'cart', component: CartComponent },
])
],
<!-- checkoutボタンを押すと/cartのURLに推移するようにrouteLinkで設定している-->
<!-- つまり/cartに遷移するとCartComponent(のページ:HTML)に推移するようにapp.moduleで設定したということである-->
<a routerLink="/cart" class="button fancy-button">
<i class="material-icons">shopping_cart</i>Checkout
</a>
実際に右上でのCheckOutボタンを押して/cartのURLにページ遷移をして、エラーなく画面が表示されればOKである。2つ程度の同じやり方の事例を見るとルーティングの設定方法が理解できるようになるのではないかと思う。
カートのアイテムを表示する
最後に、/cartに遷移した場合の画面を作成する(つまりCartComponentを編集する)。
まず、tsファイルを一気に下記に示し、コメントでコードの意味を追記する。
getItems()メソッドは上記のカートサービスを定義するで既に定義してある。
import { Component } from '@angular/core';
//CartServiceクラスが使えるようにimportする
import { CartService } from '../cart.service';
export class CartComponent {
//itemsプロパティを定義し、下記でインスタンス化したCartServiceのgetItems()メソッドを実行して商品データを受け取れるようにする。
items = this.cartService.getItems();
//CartServiceクラスをインスタンス化して実際に使用できるようにする。(DI)
constructor(
private cartService: CartService
) { }
}
続いてtsファイルで商品データを受け取り、実際にHTMLファイルを編集して表示できるようにする。
*ngForはFor文と同義であることは以前説明したため、items配列に入っている商品の分だけ表示するようにしている。currencyパイプも前回の最後に出てきたパイプと同一なのでここで再確認しても良い。
<h3>Cart</h3>
<div class="cart-item" *ngFor="let item of items">
<span>{{ item.name }}</span>
<span>{{ item.price | currency }}</span>
</div>
以上、公式ドキュメント通りに動作することが確認できればOKである。
途中エラーが出ることも多々あると思うが、ソースをコピペ、打ち込むにあたって、消しては行けない部分を消してしまったりする可能性が非常に高くなってくるので再度確認して進めてもらいたい。
おわりに
Angular公式チュートリアルの3としてデータの管理
セクションの前半部分を進めてきた。今回は主にビジネスロジックを記述するServiceという構成単位を実装したが、ルーティングや依存性の注入(DI)の設定方法は前回 と同様であるので、詰まることなく進めることができた人が多いかもしれない。実装に関して、2つ程度の例を確認するとこういうことかと理解できることが多いように思うので、今回で上述したルーティングや依存性の注入(DI)についてとりあえず理解できたのではないか。
DIについては私の表面的な理解なので、テストや実際の開発においてもっとより便利な使い方や考え方等々あると思うが、私個人で理解できたら記事を書くことにする。
下記リンクより、引き続きチュートリアルを進めていきたい。
次→【Angular】公式チュートリアルの理解4 (データの管理②)