前回は、
- Angularでのデータ管理の基礎(モデル作成)
- HTMLでの分岐(*ngIf)
- HTMLでのループ(*ngFor)
を学びました。
本記事では、
- ES6とTypescriptの基礎
- Typescriptの型定義について
- Angularでのデータ管理の基礎2
- サービスクラスの作成
をやっていきます。
この記事のソースコード
https://github.com/seteen/AngularGuides/tree/入門その5
Typescriptの基礎
Angularでは、Typescriptを利用します。
今後の入門では、Typescriptのコードが増えていきますので、このあたりで基礎を学んでおきましょう。
型の指定
すでに何度か出てきましたが、 Typescript では、変数、メソッドそれぞれに型を定義します。
export class Test {
name: string; // <- これがインスタンス変数
toString(input: string): string { // <- これがインスタンスメソッド
return input;
}
}
このように、
- 変数定義は
変数名: 型
- メソッド定義は
メソッド名(引数名: 引数名の型): メソッドの型 { メソッドの内容 }
と書く必要があります。
型の種類
Typescriptで用意されている型として、よく使うのは、
- string : 文字列
- number : 数値
- boolean : 真偽値
- void: 何も返さないメソッドの型
です。また、自分で定義したクラスなどのクラスも型となります。
また、配列については、 string[]
のように型の後ろに []
をつけることで表現します。
names: string[]
また、 なんでも良い という型の any
があります。
どの型を指定したら良いかわからないというときの最終手段です。
Interface
Typescript には、 Interface があります。
これは、Javaなどのように Class に継承するInterfaceとしても使えますが、
もう一つの使い方としてメソッドの引数などが複雑なときに利用することが多く、ObjectのKeyなどに制約をもたせることができます。
例を見ていきます。
export interface TestInterface {
name: string;
price: number;
description?: string;
}
という Interface があり、
testMethod(input: TestInterface): void {
...
}
というTestInterfaceを引数に取るメソッドが合ったとします。
Interfaceを使うことによって、少し複雑な引数に対して制約をもたせることができ、上記のコードの意味は、 name
と price
は必須で、 description
には ?
がついているので、これは optional(合ってもなくても良い)
という意味になります。
そのため、例えばこのメソッドに対して、
this.testMethod({name: 'test'});
のように実行しようとすると、 price が存在しないため、エラーが出ます。
また、下記のように存在しない要素を追加してもエラーが出ます。
this.testMethod({name: 'test', price: 100, extend: 'feawfeaw'});
Enum
また、意外とよく使うのが、 Enum です。
これは、ある変数が決まった値しか取らないときなどに使います。
例えばステータスとして、 非公開( unpublished )
, 公開 ( published )
があり、
それ以外の値をとらない変数があるような場合
export enum Status {
unpublished = 'unpublished',
published = 'published'
}
と記載します。
もし数値しか取らないで、 非公開 (0)
, 公開 (1)
であったとすれば、下記のように記載できます。
export enum Status {
unpublished,
published
}
つまり、何も指定しなければ、上から順に 0 から数値が割り当てられます。
これを使う側としては、
export class TestClass {
status: Status;
}
のように記載します。
これにより、 status
には Status
で定義した値しか入らない(静的に)ようになります。
Enum含めた型による制約の注意事項
string
, number
などの変数での型指定、 Enumによる値の制約、これらは、静的な解析時にのみ有効です。
そのため、動的に 別の型の値を入れられたとしても、その時点でエラーを起こしてくれたりはしません。(Javaなどとは違う)
これは、実行時には Javascript として実行されるため、Typescriptの恩恵は受けられないからです。例えばAPIで 本来は string を想定していたのに number が入っていたとして、
それを代入してもエラーになったりはしません。
型の制約は強力ですが、そのような場合もあるため、あまり頼りすぎて思考を停止しないように気をつけましょう。
TIPS: Typescriptの便利な記法
例えば params という変数が定義されているとすると、
{ params }
と記載するだけで、{ params: params }
と解釈してくれます。
地味に便利です。
private, protected, public
Typescript では、 private
, protected
, public
という変数やメソッドのスコープを指定する修飾子が用意されています。
修飾子 | スコープ |
---|---|
private | 定義されたクラス内 |
protected | 定義されたクラスとそれを継承しているクラス |
public | 外部からアクセス可能 |
この修飾子は、
export class Test {
private name: string;
protected testMethod(): string {
...
}
}
のように使います。何も修飾子を指定しなければ、 public
として扱われます。
Angularでのデータ管理の基礎2
Angularでは、APIアクセスやコンポーネントをまたがるデータの管理などを、 Service
クラスを使って行うことができます。
今回は、 ProductService
クラスを作って Product
のデータ管理をコンポーネントの外に出してみましょう。
サービスクラスの作成
Angular CLI を用いてサービスを作成しましょう。
# ng g service shared/services/product
CREATE src/app/shared/services/product.service.spec.ts (380 bytes)
CREATE src/app/shared/services/product.service.ts (136 bytes)
今回は、 src/app/shared/services/
ディレクトリにサービスを配置したかったので、上記のような記載にしました。
作成されたファイルを見てみましょう。 spec.ts
ファイル(テストファイルです)もできていますが、今は無視します。
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ProductService {
constructor() { }
}
@Injectable
というのは、サービスクラスに指定するおまじないです。
中に { providedIn: 'root' }
が入っていますが、これはルートレベルでこのサービスを利用するということになります。
サービスクラスは、指定した providedIn
の単位でインスタンス化され(今回だとルートレベル。つまりプロジェクト全体)、一つのインスタンスが使い回されます。
いわゆるシングルトンに近い状態と思っていただけると良いかと思います。
これに Product
のリストを取得するメソッドを追加していきます。
最終的には、ここでAPIアクセスを行うようになりますが、今はAPIアクセスしないようにします。
import { Injectable } from '@angular/core';
import { Product } from '../models/product';
import { Observable, of } from 'rxjs/index';
@Injectable({
providedIn: 'root'
})
export class ProductService {
products = [
new Product(1, 'Angular入門書「天地創造の章」', 3800, '神は云った。「Angularあれ」。するとAngularが出来た。'),
new Product(2, 'Angularを覚えたら、年収も上がって、女の子にももてて、人生が変わりました!', 410, '年収300万のSEが、Angularと出会う。それは、小さな会社の社畜が始めた、最初の抵抗だった。'),
new Product(3, '異世界転生から始めるAngular生活(1)', 680,
'スパゲッティの沼でデスマーチ真っ最中の田中。過酷な日々からの現実逃避か彼は、異世界に放り出され、そこでAngularの入門書を拾う。現実逃避でさえ、プログラミングをするしかない彼に待ち受けるのは!?'),
];
constructor() { }
list(): Observable<Product[]> {
return of(this.products);
}
}
list()
メソッドを追加しました。型は、 Observable<Product[]>
を指定しています。
現状では、API通信がないので同期処理になりますが、最終的にはAPI通信を行うので list()
メソッドは非同期処理になります。
Angularでは、非同期処理には RxJS
を利用します。 Observable
というのは RxJS
のクラスで、非同期処理におけるメインのクラスになります。
今の所は、 Promise
のようなものだと思っていただければ良いかなと思います。
of
というのも RxJS
のメソッドで、これは与えられた内容で Observable
を作るというものです。
つまり上記コードは、 Observable
(観測可能) な Product
の配列を返すメソッドになります。
product-list.component.ts
の変更
次に、 product-list.component.ts
を変更していきます。
import { Component, OnInit } from '@angular/core';
import { Product } from '../../shared/models/product';
import { ProductService } from '../../shared/services/product.service';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
products: Product[] = null;
constructor(
private productService: ProductService,
) {}
ngOnInit() {
this.productService.list().subscribe((products: Product[]) => {
this.products = products;
});
}
}
元々は ngOnInit
内で Product
の配列を作成していましたが、 ProductService
を利用するようになりました。
constructor
の解説
constructor
で ProductService
の引数を呼んでいます。
Angularでは、この記述により ProductService
をコンポーネントやサービスに挿入できます。 (いわゆる DI )
つまり、この記載で ProductListComponent
に、 productService
という変数が生成されたことになります。
ngOnInit の解説
this.productService.list()
は、 Obserbable<Product[]>
を返します。
先程記載した通り、これは Observable
(観測可能) な Product
の配列を返します。
subscribe
は、 Observable
のメソッドで対象の Observable
の観測を開始するものです(APIアクセスの非同期処理を開始)。
観測が完了すると、 subscribe
の引数のコールバックメソッドが呼ばれます。この引数には、 Observable<Product[]>
の Product[]
が入ることになります。
そのため、 ProductService
の list()
メソッドで返した配列がこのメソッドに入ります。
つまり、
this.productService.list().subscribe((products: Product[]) => {
の products
に入っているということになります。
このメソッドの中で
this.products = products;
としてインスタンス変数に代入しています。
これにより、product-list.component.ts
の products
を表示するように記載しているViewに反映されます。
ただ、今回で、前回の 3秒待つ という記述を削除したので、少しだけ挙動が変わりました(すぐに商品一覧が表示される)。
動作確認
ng serve
で起動して、 http://localhost:4200/products
にアクセスして挙動を確認してみましょう。
ここまでのコミット
product-detail.component.ts からの ProductServiceの利用
続いて、 product-detail.component.ts
から ProductService
を呼ぶように修正してみましょう。
ProductServiceにidで商品が取得できるメソッドを追加する
ProductService
に、 get(id)
を追加します。
import { Injectable } from '@angular/core';
import { Product } from '../models/product';
import { Observable, of } from 'rxjs/index';
@Injectable({
providedIn: 'root'
})
export class ProductService {
products = [
new Product(1, 'Angular入門書「天地創造の章」', 3800, '神は云った。「Angularあれ」。するとAngularが出来た。'),
new Product(2, 'Angularを覚えたら、年収も上がって、女の子にももてて、人生が変わりました!', 410, '年収300万のSEが、Angularと出会う。それは、小さな会社の社畜が始めた、最初の抵抗だった。'),
new Product(3, '異世界転生から始めるAngular生活(1)', 680,
'スパゲッティの沼でデスマーチ真っ最中の田中。過酷な日々からの現実逃避か彼は、異世界に放り出され、そこでAngularの入門書を拾う。現実逃避でさえ、プログラミングをするしかない彼に待ち受けるのは!?'),
];
constructor() { }
list(): Observable<Product[]> {
return of(this.products);
}
get(id: number): Observable<Product> {
return of(this.products[id - 1]);
}
}
get(id)
メソッドは、idで指定されたProductの Observable
を返すというものです。
product-detail.component.ts
からの利用
ほぼ product-list.components.ts
と同様です。
import { Component, OnInit } from '@angular/core';
import { Product } from '../../shared/models/product';
import { ProductService } from '../../shared/services/product.service';
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.component.html',
styleUrls: ['./product-detail.component.scss']
})
export class ProductDetailComponent implements OnInit {
product: Product;
constructor(
private productService: ProductService,
) {}
ngOnInit() {
this.productService.get(2).subscribe((product: Product) => {
this.product = product;
});
}
}
-
constructor
にて ProductService の変数を得る -
ngOnInit
でget(id)
をsubscribe
(観測) して商品を取得しています - あと、特に理由はありませんが、取得するものを
2
に変えました
動作確認
ng serve
で起動して、 http://localhost:4200/products/test
にアクセスしてみましょう。
ここまでのコミット
まとめ
今回は、Typescriptの基礎 と サービスクラスを使ったデータ管理の方法を学びました。
Observable
という非同期処理の話が出てきました。
少し難しく感じたかもしれませんが、これも使っていくうちにわかっていきますので(私も最初はわけわからん、と思いつつ使っていて自然とわかるようになりました)、まずは使っていきましょう。
次回は、 Angularでのフォームの作り方や、クリックイベントの受け取り方などを解説していきます。
Angular入門 未経験から1ヶ月でサービス作れるようにする その6. ngModelを使ったフォーム
入門記事一覧
「Angular入門 未経験から1ヶ月でサービス作れるようにする」は、記事数が多いため、まとめ記事 を作っています。
https://qiita.com/seteen/items/43908e33e08a39612a07