Angular 入門してみる5
そういえば先日Angular8がリリースされました。
今回はServiceとLifeCycleHookと,非同期処理,また Promise を用いた非同期処理について少しやる。
はじめは概要,実装はココから
関連記事
環境
- Angular CLI: 8.0.1
- Node: 12.3.1
Service,LifeCycleHook,非同期処理,Promise について
① Serviceとは?
データ共有の方法の一つ。ゆくゆくはデータベースからデータを引っ張ってくるため実装。② LifeCycleHookとは?
コンポーネントの生成直後から破棄されるまでの流れ。らしい。constructor実行後
ngOnChanges
ngOnInit
ngDoCheck
ngAfterCntentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked
ngOnDestroy
の順で実行される。らしい。
今回扱うのは ngOnInit で,こちらは constructor による初期化後,更に言うと @Input で設定された値が処理された後に一度だけ実行される。らしい。つまり初期化時に,@Input に関係する処理を行いたい場合,間に合わない(constructor では@Input の処理は行われていないため)。
よって,Angularコンポーネントでは,初期化処理はngOnInitを用いる。
③ 非同期処理とは?
同期的でない処理。そのまますぎる。例を見たほうが早い。まずは同期処理の例。
console.log("start"); sleep(milliSeconds) { let startTime = new Date().getTime(); while (new Date().getTime() < startTime + milliSeconds); console.log("sleepが完了。"); } sleep(10000); // 10秒経過するまで何もしない console.log("end");
実行。
start sleepが完了。 end
上から順番に実行される。
次に非同期処理の例。
1 2
console.log("start"); let f = require('f'); f.readFile("example.txt", "utf-8", function(err, data){ if (err) throw err; console.log("ファイルの読み取り準備ができました。"); console.log(data); }); console.log("end");
実行。
start end ファイルの読み取り準備ができました。 1 2
endが先に出力される。f.readFileは非同期型関数として扱われ,中の処理が完了した段階で呼び出し元に通知する(コールバック)。
コールバック関数は度々ネストが深くなり,複雑になる傾向がある。
したがってできるだけ同期的に書きたい。どうする? → 同期的に書く。
具体的には? → 複数あるが,ここでは Promise を用いる。
④ Promise って?
本来返したい値をプロミスオブジェクトとして持たせ,返せる場合,プロミスオブジェクトを通して呼び出し元に値を返す。ネストが深くならず,分かりやすい程度しかまだ知らないけどその認識で合ってる?
console.log('start'); function puts(str) { return new Promise(function(resolve) { // 1. Promise を new して、promiseオブジェクトを返す setTimeout(function() { resolve(str); }, 1000); }); } // 2.1 のpromiseオブジェクトに対して .then で値が返ってきた時のコールバックを設定する。 puts('async').then(function(result) { console.log(result) }); console.log('end');
実行
start end async
最後にasyncが返り非同期処理が完了した。
実際に社員名簿管理アプリに実装
ようやく本題に入れる。外部データの作成
まずは,app.component.ts にいたメンバーの配列 MEMBERS を別ファイルに移動。
ここでは mock.members.ts というファイルを新規作成し移動。
import {Member} from './member'; // 外部参照するため export しておく export const MEMBERS: Member[] = [ { id: 10, name: 'test10' }, { id: 11, name: 'test11' }, { id: 12, name: 'test12' }, { id: 13, name: 'test13' }, { id: 14, name: 'test14' }, { id: 15, name: 'test15' }, ];
app.component.ts の members の初期化を,Memberクラスの配列に修正しておく。
export class AppComponent { title = '自社社員名簿'; members: Member[]; // 修正 selectedMember: Member; onSelect(member: Member): void { this.selectedMember = member; } }
Service の実装
つづいて,この mock.members.ts を共有させる役割を持つService を作成する。ここで,ファイル名 member.service.ts を作成。
mock.members.ts の MEMBERS を受け取る getMembers メソッドを定義しておく。これを使い回すことで共有化を実現。
import {Injectable} from '@angular/core'; import {MEMBERS} from './mock.members'; import {Member} from './member'; @Injectable() export class MemberService { getMembers(): Member[] { return MEMBERS; } }
Service は app.module に providerとして記述することで扱うことができる。
NgModule デコレータの中に追記。
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { MemberDetailComponent } from './member-detail.component'; import {MemberService} from './member.service'; // 追記 @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, MemberDetailComponent, ], bootstrap: [ AppComponent ], providers: [ MemberService // 追記 ] }) export class AppModule { }
これで Service が提供できる体制が整う。
LifeCycleHook の実装
1. 先述の通りLifeCycleHook は constructor ののち実行されるので,constructor を記述し,ここでmemberServiceを呼び出す。
2. LifeCycleHook の メソッドの1つ ngOnInit() を用いる。インターフェイスであるため,import だけでなく implements で継承しなくてはならない。これで ngOnInit() の実装が強制されるので,getMembers() を発火。
import { Component, OnInit } from '@angular/core'; // import import { Member } from './member'; import {MemberService} from './member.service'; // こちらも忘れずにimport @Component ({ ... }) export class AppComponent implements OnInit { title = '自社社員名簿'; members: Member[]; selectedMember: Member; constructor(private memberService: MemberService) { // constructor の引数にmemberService を持ってくる } ngOnInit() { // OnInit 継承による強制メソッド。constructorによる初期化が終わったら発火。 this.members = this.memberService.getMembers(); // memberService の getMembers() を発火。 } onSelect(member: Member): void { this.selectedMember = member; } }
一旦実行。問題なく動いているが,同期的であり,非同期処理を施す必要がある。
Promise オブジェクトを用いた非同期処理の実現
Promise の実装は思いのほか楽。
まずは Promise 型にしたい memberService の getMembers() から。Promiseで返したい関数の型に Promise 型を指定し,返り値を resolve で返してやる。
import {Injectable} from '@angular/core'; import {MEMBERS} from './mock.members'; import {Member} from './member'; @Injectable() export class MemberService { getMembers(): Promise<Member[]> { return Promise.resolve(MEMBERS); } }
次にapp.component.ts の ngOnInit() で,先程の関数から Promise オブジェクトを受け取り非同期処理を実行する。
import { Component, OnInit } from '@angular/core'; // import import { Member } from './member'; import {MemberService} from './member.service'; // こちらも忘れずにimport @Component ({ ... }) export class AppComponent implements OnInit { title = '自社社員名簿'; members: Member[]; selectedMember: Member; constructor(private memberService: MemberService) { // constructor の引数にmemberService を持ってくる } ngOnInit() { // this.members = this.memberService.getMembers(); this.memberService.getMembers() .then(member => this.member = member); // then: resolve の場合の処理。 // thenで帰ってきた Promise 型オブジェクト(member) を this.member に代入。 } onSelect(member: Member): void { this.selectedMember = member; } }
ここで,ngOnInit() 内の then() 内の 「members」は Promise で返ったオブジェクトを,「this.members」はAppComponent クラスで定義した members プロパティであることに注意。
実行。動作は変わらないが,内部では非同期処理となっている。
以上。