web-dev-qa-db-ja.com

カスタムディレクティブのng-changeを実装する方法

私は次のようなテンプレートを持つディレクティブを持っています

<div>
    <div ng-repeat="item in items" ng-click="updateModel(item)">
<div>

私のディレクティブは次のように宣言されています:

return {
    templateUrl: '...',
    restrict: 'E',
    require: '^ngModel',
    scope: {
        items: '=',
        ngModel: '=',
        ngChange: '&'
    },
    link: function postLink(scope, element, attrs) 
    {
        scope.updateModel = function(item)
        {
             scope.ngModel = item;
             scope.ngChange();
        }
    }
}

私はを頂きたい ng-changeアイテムがクリックされ、fooの値が既に変更されたときに呼び出されます。

つまり、私のディレクティブが次のように実装されている場合:

<my-directive items=items ng-model="foo" ng-change="bar(foo)"></my-directive>

barの値が更新されたら、fooを呼び出すことを期待します。

上記のコードでは、ngChangeは正常に呼び出されますが、新しい更新値ではなく、古い値のfooを使用して呼び出されます。

この問題を解決する1つの方法は、ngChangeの値がすでに変更されているときに、将来のある時点でタイムアウト内でfooを呼び出して実行することです。しかし、この解決策は、物事が実行されることになっている順序を私にゆるやかに制御させます。

親スコープでfooを介してウォッチャーを使用することもできますが、このソリューションではngChangeメソッドを実装することはできず、ウォッチャーは優れたメモリコンシューマーであると言われています。

ngChangeをタイムアウトやウォッチャーなしで同期的に実行する方法はありますか?

例: http://plnkr.co/edit/8H6QDO8OYiOyOx8efhyJ?p=preview

51
htellez

ngModelが必要な場合、ngModelController$setViewValueを呼び出すだけで、ng-changeを暗黙的に評価できます。リンク関数の4番目のパラメーターはngModelCtrlである必要があります。以下のコードは、ディレクティブに対してng-changeを機能させます。

link : function(scope, element, attrs, ngModelCtrl){
    scope.updateModel = function(item) {
        ngModelCtrl.$setViewValue(item);
    }
}

ソリューションが機能するためには、ngChangeとngModelをmyDirectiveの分離スコープから削除してください。

ここにプランクがあります: http://plnkr.co/edit/UefUzOo88MwOMkpgeX07?p=preview

58
Samuli Ulmanen

tl; dr

私の経験では、 ngModelCtrl から継承する必要があります。メソッド_ng-change_を使用すると、_ngModelCtrl.$setViewValue_式が自動的に評価されます

_angular.module("myApp").directive("myDirective", function(){
  return {
    require:"^ngModel", // this is important, 
    scope:{
      ... // put the variables you need here but DO NOT have a variable named ngModel or ngChange 
    }, 
    link: function(scope, elt, attrs, ctrl){ // ctrl here is the ngModelCtrl
      scope.setValue = function(value){
        ctrl.$setViewValue(value); // this line will automatically eval your ng-change
      };
    }
  };
});
_

より正確に

_ng-change_は、ngModelCtrl.$commitViewValue()[〜#〜] if [〜#〜]の間に評価されますngModelが変更されました。トリガー引数を使用しない場合、または ngModelOptions を正確に指定していない場合、メソッド$commitViewValue()$setViewValue(value, trigger)によって自動的に呼び出されます。

_ng-chage_の参照が変更されると、_$viewValue_が自動的にトリガーされるように指定しましたifngModelstringまたはintである場合、心配する必要はありません。 ngModelがオブジェクトであり、そのプロパティの一部を変更するだけの場合、_$setViewValue_はngChangeを評価しません。

投稿の最初からコード例を取り上げると

_scope.setValue = function(value){
    ctrl.$setViewValue(value); // this line will automatically evalyour ng-change
};
scope.updateValue = function(prop1Value){
    var vv = ctrl.$viewValue;
    vv.prop1 = prop1Value;
    ctrl.$setViewValue(vv); // this line won't eval the ng-change expression
};
_
14
lucienBertin

いくつかの調査の後、最良のアプローチは$timeout(callback, 0)を使用することであると思われます。

$digestコールバックが実行された直後にサイクルします。

だから、私の場合、解決策は使用することでした

$timeout(scope.ngChange, 0);

この方法では、コールバックのシグネチャが何であるかは関係ありません。親スコープで定義したとおりに実行されます。

このような変更を加えたプランカーは次のとおりです:http://plnkr.co/edit/9MGptJpSQslk8g8tD2bZ?p=preview

9
htellez

Samuli UlmanenとlucienBertinの答えはそれを否定していますが、AngularJSのドキュメントを少し読むと、これを処理する方法についてさらにアドバイスが提供されます( https://docs.angularjs.org/api/ng/type/ngModelを参照してください。 NgModelController )。

特に、オブジェクトを$ setViewValue(myObj)に渡す場合。 AngularJS Documentatationの状態:

標準入力で使用すると、ビューの値は常に文字列になります(input [date]のDateオブジェクトなど、別の型に解析される場合もあります)。ただし、カスタムコントロールはこのメソッドにオブジェクトを渡すこともあります。この場合、$ setViewValueに渡す前にオブジェクトのコピーを作成する必要があります。これは、ngModelはオブジェクトの詳細な監視を実行せず、IDの変更のみを探すためです。オブジェクトのプロパティを変更するだけの場合、ngModelはオブジェクトが変更されたことを認識せず、$ parsersおよび$ validatorsパイプラインを呼び出しません。このため、$ setViewValueに渡されたコピーのプロパティを変更しないでください。そうしないと、スコープのモデル値が誤って変更される可能性があります。

私の特定のケースでは、私のモデルは瞬間日付オブジェクトなので、setViewValueを呼び出す前にオブジェクトを複製する必要があります。幸運なことに、ここでは簡単なクローンメソッドvar b = moment(a);を提供しています。

link : function(scope, elements, attrs, ctrl) {
    scope.updateModel = function (value) {
        if (ctrl.$viewValue == value) {
            var copyOfObject = moment(value);
            ctrl.$setViewValue(copyOfObject);
        }
        else
        {
            ctrl.$setViewValue(value);
        }
    };
}
0
Arkiliknam