Part6: サービス

穂乃果「LVだけどチケット取れたよっ!」
ことり「明神様のおかげだね♪」
海未「別件で神田明神へ行ったのですが、そこでAqours 1stライブ当選祈願の絵馬を見かけまして・・・」
ことり「今日が一般発売日だって思い出して、その場で申し込んだら取れたんだよね」
海未「TOHOシネマズ海老名へいらっしゃる皆さん、よろしくお願いしますね♡」


海未「さて、今回はデータの扱いの話です。これまではapp.component.tsに以下のように直接書かれていましたね?」

海未「こうしたデータの定義は、本来コンポーネントの中で行うべきものではありません。複数コンポーネントで共有することが難しいというのもありますし、データソースはファイルかもしれませんし、DBかもしれませんし、ネットワークの向こうのAPIかもしれません」
ことり「もうちょっと抽象化しないとだめってことかな」
海未「そうですね。Angularではそのために、サービスという仕組みが用意されています。これから実際に、IdolServiceを使うようにコードを修正していきましょう。idol.service.tsを作成します」

穂乃果「また知らないデコレータが出てきた・・・」
海未「Dependency Injection、略してDIという仕組みがあって、@Injectableはインジェクション可能なクラスを表すデコレータです」
ことり「DI・・・?」
海未「最初は言葉で説明するより、実際にコードを見て行く方がわかりやすいと思うので・・・その言葉は一旦置いておきましょうか」


海未「サービスはデータソースとコンポーネントの橋渡しをするものです。データソースの方を用意しておきましょう。mock-idols.tsを作成します」

海未「IdolServiceからはこれを読み込むことにしましょう」
穂乃果「app.component.tsの方は消しちゃってもいいの?」
海未「そうですね。app.component.tsからはIDOLSを削除して、AppComponentidolsプロパティもidols: Idol[];としてください」


海未「IdolServiceに戻りますね。mock-idols.tsからデータを読み出して返すメソッドを用意しましょう」

ことり「うん、たださっきのを返すだけだね」


海未「ではいよいよ、DIに触れていきます。このIdolServiceAppComponentから使うことを考えます」
穂乃果「えーっと、コンストラクタでnew IdolService()してthis.idols = idolService.getIdols()だねっ!」
海未「ぶっぶー!ですわ」
ことり「え」
穂乃果「海未ちゃん・・・?」
海未「・・・・・・あっ///」
穂乃果「うんうん、わかるよ海未ちゃん」
ことり「気に入った台詞、ついつい言いたくなっちゃったんだよね」
穂乃果「あるよねー」
ことり「言わないけどねー」
海未「あ、憐れむような目で見るのはやめてください!・・・もう」

海未「ともかく!newを使ってサービスをインスタンス化するのはいくつかの理由から避けるべきなのです」

  • IdolServiceのコンストラクタに修正が入った場合の影響範囲を局所化できない
  • インスタンスやデータのキャッシュや共有ができない
  • AppComponentIdolServiceの実装に依存する

海未「いずれも、注意深く実装することで対処することもできるのですが、DIを使うと簡単かつ確実に、保守性が高く効率的なコードを書くことができます」
ことり「ちょっと難しいかも」
海未「まあ、そのうち自然に理解できますよ。では、app.component.tsから修正していきましょう。まずはいつものように、サービスクラスをインポートします」

海未「そしてここが肝です。AppComponentのコンストラクタをこのように実装します」

穂乃果「あれ?コンストラクタに渡されてくるの?」
海未「AppComponentをインスタンス化するのはAngularです。その際に、適切なIdolServiceのインスタンスを渡してくれるのです」
穂乃果「それで、こっちでnewする必要がないんだね」
海未「Angularが良きに計らってくれますから、難しく考えずに受け取ったインスタンスを使えば大丈夫です」
ことり「ヨキニハカラエ、ミナノシュウ!」


海未「あと一手間必要です。AppComponent@Componentデコレータに、AppComponentIdolServiceを使うことを示す記述が必要です」

海未「これで、IdolServiceをDIして利用できるようになりました。ではこのように、サービスからデータを取得するメソッドを用意しましょう」

穂乃果「こっちはぶっぶーじゃなかったかな?」
ことり「待って穂乃果ちゃん、これコンストラクタでやってるわけじゃないよ」
海未「そうです。この処理はコンストラクタで行うべきではありません・・・半ば経験則として、コンストラクタに重い処理を書くべきではないのです」
穂乃果「だからgetIdolsってメソッドになってるのか・・・でもさ、コンストラクタじゃないなら、このメソッドを呼ぶのはどこなのかな?」
海未「こういった初期化処理に適した仕組みが、Angularには用意されています。OnInitというインターフェイスがそれです」

海未「OnInitをインポートして、AppComponentクラスはそれを実装するようにします」

海未「そして、OnInitインターフェイスで定義されているngOnInitメソッドを実装して、そこで初期化処理を行います」

海未「このngOnInitは、インスタンスが生成された後に然るべきタイミングでAngularが呼び出してくれます」
ことり「細かいことは、Angularにお任せ、なんだね」
海未「アプリケーションロジックだけ書いたら、あとは穂乃果が得意な丸投げでよいのです」
穂乃果「ひどいよっ!」


海未「最後に、簡単な非同期処理を見ておきましょう。将来的に、データをファイルやDB、WebAPIなどアクセスに時間のかかる場所から取得することを考えてみてください」
穂乃果「APIからデータ取ってくるのに10秒かかるとして・・・えーと、待つしかないんじゃないかな?」
海未「待つしかない、というのは確かにそうなのですが、待っている間何もできないのは勝手が悪いでしょう」
ことり「たしかに・・・他にもやりたい処理とかあるよね」
海未「そのために、ES2015で導入されたPromiseを使います。そういえば、言語仕様のときには扱いませんでしたね」
穂乃果「プロミス・・・」
ことり「ご利用は・・・」
ほのこと「「計画的に!」」
海未「何の話をしているのですか!」

海未「Promiseというのは、この処理が終わったら値を返すから先に進んでいてください、という文字通りの約束です。HeroServiceのコードはこうなります」

海未「resolveは処理を渡すと、Promiseオブジェクトを返すと同時に処理を始めます。ですから、この場合getIdolsが値を返した時点ではPromiseオブジェクトには結果は入っていません」
ことり「データがそろってから値が入る・・・みたいな感じだよね?受け取り側はどうするの?」
海未「AppComponentはこのようになります」

海未「IdolServicegetIdolsPromise<Idol[]>を返すようになりました。それに対してthenを使うことで、Promiseの処理が終了したときに実行される処理を登録できます」
ことり「アロー関数だね・・・穂乃果ちゃんが必死で昔のノートをめくってるけど」
海未「・・・もう放っておきましょう」
ことり「あはは・・・」
海未「ここで、Promiseの型パラメータはIdol[]でした。ですから、thenに渡す関数の引数はIdol[]型になります。あとは分かりますね」
ことり「Promiseの処理が終わるとデータがその無名関数に渡されるから、そこでプロパティに値を設定してあげればいいんだね」


海未「というわけで、今回実装したidol.service.tsのコードはこのようになります」

海未「app.component.tsはこのように」

海未「次回は画面遷移を司るルーターについて見ていきましょう」


LINEで送る
Pocket