今回は各ページや画面部品(まとめて Component と呼びます)のライフサイクルについて解説します。
Component ライフサイクル
ページや部品が画面にバインドされるときや表示されるとき、または画面から消えてゆくときに任意のロジックを実行したいという場面はよくありますよね。そんな時に任意に定義できるホックメソッドがあります。
Component ライフサイクルホック
import {inject} from 'aurelia-framework'
import {SomeService} from 'some-service'
@inject(SomeService)
export class SomeComponent {
constructor(someService) {
// コンストラクタは DI のためのものと心得る
this.someService = someService;
}
created(owningView, myView) {
// constructor の次、View が生成されたときに呼ばれる
}
bind(bindingContext, overrideContext) {
// View との data binding が有効になったときに呼ばれる
}
attached() {
// DOM に組み込まれたとき(表示されたとき)に呼ばれる
}
detached() {
// DOM から消えたときに呼ばれる
}
unbind() {
// View との data binding が解除されたときに呼ばれる
}
}
constructor
基本的に Component を自分でインスタンス化することはありません。Component をインスタンス化するのは DI コンテナの仕事で、依存を解決するという側面を持っています。
@inject
で宣言することで、コンストラクタの引数として必要なサービスが手に入ります。DI を使う場合は必ず書くことになります。
created
View, ViewModel が生成され、Aurelia の内部的なコントローラに登録されたタイミングで呼び出されます。
第一引数 owningView
には Component が配置される View
第二引数 myView
には ViewModel に紐づく View がそれぞれ渡されます。
bind
View と ViewModel のデータバインディングが有効になったときに呼び出されます。
第一引数 bindingContext
にはこの Component がバインドされる際のコンテキスト情報が渡されます。
第二引数の overrideContext
はよりテクニカルな目的のためにあります。
親階層をトラバースするための情報を含むとともに、任意のコンテキストプロパティを追加するために使用することができます。
attached
こちらはなにかと使うことが多いです。
この Component が晴れて DOM に追加されたときに呼び出されます。
DOM にアクセスする必要があるならこのタイミングです。
たとえば View で ref
を使うことで任意の要素にアクセスできるようになります。
<template>
<p ref="elm">サンプル</p>
</template>
export class Sample {
attached() {
console.log(this.elm);
// -> <p ref="elm" class="au-target" au-target-id="3">サンプル</p>
}
}
detached
DOM から取り除かれたときに呼び出されます。
後始末として必要な処理がある場合に便利です。
unbind
detached よりも後に呼び出されますが、違いはまだよくわかりません。。。
名前からしてデータバインディングが解除された後に呼び出されるものと思われます。
画面遷移ライフサイクル
こちらはルーティングによって画面遷移が起きるときに呼び出されるメソッドです。
画面遷移ライフサイクルホック
export class SomePage {
canActivate(params, routeConfig, navigationInstruction) {
// このページに遷移しようとするときに呼ばれる
// false を返却することでキャンセルすることができる
}
activate(params, routeConfig, navigationInstruction) {
// このページに遷移するときに呼ばれる
// ルート設定に '/:name' と書けば、このようにパラメータを取得できる。
console.log(params.name);
}
canDeactivate() {
// ほかのページに遷移しようとするときに呼ばれる
// false を返却することでキャンセルすることができる
}
deactivate() {
// ほかのページに遷移するときに呼ばれる
}
}
canActivate
このページに遷移してもよいかどうかをルータに教えるためのホックです。
false を返却すれば画面遷移はキャンセルされ、ブラウザの現在の URL も遷移する前のものに戻ります。
第一引数 params
はルーティング設定時にパラメータ化した部分の値がオブジェクトとして渡されます。
export class App {
configureRouter(config, router) {
config.title = 'Aurelia';
config.map([
{ route: ['', 'welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title: 'Welcome' },
{ route: 'user/:name', name: 'user', moduleId: 'user' } // <- :name の部分がパラメータ
]);
this.router = router;
}
}
第二引数 routeConfig
は該当するルート設定、
第三引数 navigationInstruction
はなんやかんやいろんな情報が含まれています。(console.log(navigationInstruction)
で表示してみるとよいです)
routeConfig.navModel
でグローバルナビゲーションに対応するモデルが取れます。
routeConfig.navModel.setTitle(param.name);
とすれば動的にメニューの表示を変更することもできます。
activate
このページに遷移するとき、準備をするためのホックです。
引数は canActivate とまったく同じです。
canDeactivate
このページから離れてもよいかどうかをルータに教えるためのホックです。
false を返却すれば画面遷移はキャンセルされ、このページを表示し続けます。
deactivate
このページから離れるとき、後処理をするためのホックです。
非同期処理
Promise を使う
画面遷移ライフサイクルホックは、すべて非同期処理に対応しています。
例えばホックのロジックの中でサーバと通信する場合、そのままだと通信が終わるまで待ってくれませんよね。
activate(params) {
this.client.get('api/getItem', { id: params:id })
.then(res => this.item = res.item);
}
上記の例では、通信が終わる前に画面遷移が完了してしまいます。もちろんそれでもよい場合は少なくないかもしれませんが、画面に表示する内容が準備できるまで待ってほしいということもあります。
そういう時は、Promise を返せばOKです。
activate(params) {
return new Promise((fulfilled, rejected) => {
this.client.get('api/getItem', { id: params:id })
.then(res => {
this.item = res.item;
fulfilled(); // このタイミングで遷移が完了する
});
});
}
canActivate, canDeactivate でも同じく Promise を返却しておいて、fulfilled(false)
とすればキャンセルすることが可能です。
ES7 async/await を使う
Aurelia で async/await を使う場合、 build/babel-options.js
に "runtime"
を追記します。
module.exports = {
modules: 'system',
moduleIds: false,
comments: false,
compact: false,
stage: 2,
optional: [
"es7.decorators",
"es7.classProperties", // <- カンマを忘れずに
"runtime" // <- 追記 これで async/await が使えるようになる
]
};
gulp watch
を実行している場合は、一度 Ctrl+C で止めて再起動してください。
例としてひな形 (skeleton-navigation) の users ページの activate を async/await で書き直してみます。
// 省略
@inject(HttpClient)
export class Users {
heading = 'Github Users';
users = [];
// 省略
activate() {
// Promise を返却している
return this.http.fetch('users')
.then(response => response.json())
.then(users => this.users = users);
}
}
// 省略
@inject(HttpClient)
export class Users {
heading = 'Github Users';
users = [];
// 省略
// Promise が返却される
async activate() {
let res = await this.http.fetch('users');
this.users = await res.json();
}
}
async を付けることで必ず Promise が返却されるようになりますので、Aurelia 側から見たら挙動は変わっていません。
同期処理と同じように記述できるので非常にシンプルになりますね。
さらに try ~ catch でエラーハンドリングしてあげるとよいかと思います。