ふらぱいく

備忘録。いい加減つける

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 プロパティであることに注意。

実行。動作は変わらないが,内部では非同期処理となっている。
f:id:noinoi_monstS4l:20190604012645p:plain

以上。