AngularJS1.x系で子Componentから親Componentへパラメーター付きのイベント通知を発行する

typescript TypeScript
angular-icon AngularJS

近頃久しぶりに仕事でAngularJSの1.x系を使っています。
個人的には別物になってしまったAngular2よりかなりしっくりくるので今後も業務系アプリには使ってもいいかな、なんていうところで。

scopeの$broadcast$emitは使わない

scopeの$broadcast$emitは手軽でとっても便利ですが、イベント名が重複してどこかで予期せぬ処理が走っちゃったり。引き継ぎが困難だったり。とにかく数えられない程の問題点が発生します。
(TypeScriptでイベント名や引数を管理すればある程度の軽減は可能。)

いろんな方法あるとは思いますが、私の場合はシンプルにComponentの属性としてイベント(のようなもの)を定義しそれを呼び出し元のControllerで受け取るという方法を取ります。

ざっくりこんなイメージ

<child on-change="$ctrl.change($value)"></input-date>

この場合パラメーターをどう受け渡すかという問題があります。
これがまたググっても意外と出てこなかったので、ng-changeの実装部分を覗いて理解しました。

子Component上のinputへ文字が入力されるタイミングで、親Componentに都度変更内容を通知したい場合

parent.ts
class Parent {
    static $inject = []
    constructor() {}

    private change($value: string) {
        // 子Componentのinputに入力された値が出力される
        console.log($value)
    }
}

angular.module('App').component('parent', {
    controller: Parent,
    bindings: {},
    template: '<child on-change="$ctrl.change($value)"><child>'
})

child.ts
class Child {
    private value: string
    // こいつが今回のキモ。引数にオブジェクト渡しておくと 
    // on-change="$ctrl.change($value)" というふうにプロパティを指定して受け渡すことができる。
    private onChange: (param: {$value: string})=> void

    static $inject = []
    constructor() {}

    private change() {
        // 通知する
        this.onChange({$value: this.value})
    }
}

angular.module('App').component('child', {
    controller: Child,
    bindings: {
        onChange: '&'
    },
    template: '<input ng-model="$ctrl.value" ng-change="$ctrl.change()"><input>'
})

この例はとってもシンプルですがいろいろなケースで使用できます。
$broadcast$emitよりグッと疎結合さが増しますね。最高。

例がざっくり過ぎるせいでvalue自体を=で受け渡してしまえばええやん、と思う方もいるかもしれませんが、そういう方は親Componentから受け渡した値を子Componentで編集させるが、OKボタンが押させた場合のみ親Componentへ編集内容を反映するなんていうパターンを考えてみてください。