Angular.js: 分離スコープのモデル更新の前にOnchangeがトリガーされます

作成日 2013年10月21日  ·  11コメント  ·  ソース: angular/angular.js

分離スコープを持つ要素のOnchangeイベントは、モデルの更新前にトリガーされます。

例:

<!doctype html>
<html ng-app="App">
    <head>

        <link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" />

        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.js"></script>
        <script src="http://code.angularjs.org/1.0.6/angular.js"></script>
        <script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>

        <script type="text/javascript">
            "use strict"

            var App = angular.module('App', []);
            App.controller('Ctrl', function ($scope,$timeout) {

                $scope.change1 = function () {
                    console.warn("inside change", $scope.y);
                }

                $scope.change2 = function () {
                    console.warn("inside change", $scope.x);    
                }

            });

            App.directive("wminput", function () {
                return {
                    restrict: "E",
                    replace: true,
                    scope: {
                        "onChange":"&",
                        "value": "="
                    },
                    template: '<input type="text" data-ng-model="value" data-ng-change="onChange();">'
                }
            });

        </script>

    </head>
    <body>
        <div data-ng-controller="Ctrl">
            "Onchange will be triggered after the model update"
            <input data-ng-change="change1()" data-ng-model="y">
            <br><br>
            "Onchange will be triggered before the model update"
            <wminput on-change="change2()" value="x"></wminput>
        </div>

    </body>
</html>
$compile forms low works as expected confusing bug

最も参考になるコメント

ナレッツ、私は強く反対します。 コードを消費する開発者との間で、双方向バインディングが優先されるという契約が結ばれています。 バインディングのチェーンに沿って変更が加えられた場合は、続行する前にチェーン全体を更新する必要があります。 したがって、別のディレクティブがこのディレクティブを継承し、さらに何かがバインディングのバインディングにバインドされている場合でも...そのモデルが更新された場合、スタックの最下部にある元のコントローラースコープモデルは、前の同じダイジェストサイクルで更新される必要があります。他の機能に進みます。 それが消費者に期待されていることです。

全てのコメント11件

興味深いコーナーケース... http://plnkr.co/edit/lsrga8qMcbgNWATrVht3?p = preview

これは、onChange関数が親スコープの幅「&」バインディングに委任されている場合にのみ発生します。

私たちはangularjsに基づくRADツールに取り組んできました。
アプリケーション内のすべてのウィジェットにisolateScopeがあるため、これは大きなバグでした。
onChangeのプロキシメソッドを作成することで問題を解決しました。

見てください//studio.wavemaker.com/

よろしく、Vinay

このプロキシの例とその使用方法を投稿していただけますか? これをデバッグするのに役立つかもしれません。

http://plnkr.co/edit/Xe6ErQrg7WL6aAIoM9Gd?p=previewの例を参照してください。

すぐに修正されますか? すっごく紛らわしいです。

さらに検討した結果、これは予想される動作であると言わざるを得ません。plnkrの例では、ngModelの値はコントローラーとディレクティブスコープの間の2つのバインドされた文字列です。 これで、ディレクティブの値を変更するときに、ngModelは最初に_isolate_スコープを更新し、同じダイジェストでngChangeを起動します。 双方向バインディングはまだディレクティブスコープへの変更を取得していないため、親スコープのngChangeはxの「古い」値を取得します。 双方向バインディングウォッチャーは、値を安定させるために少なくとも2つのダイジェストサイクルを必要とするため、コントローラースコープはngChangeが呼び出された後にのみ更新されます。
私はこれが紛らわしいことに同意します、しかしそれはそれがどのように機能するかです。 これを回避するには、ngModelにバインドする値を保持するディレクティブにオブジェクトをバインドします。 ngChangeが実行されたときにそのように、値が単離およびコントローラ範囲を同時に且つ利用可能な両方が参照により設定されている: http://plnkr.co/edit/fs7S6yX1a5aeo1Ese522?p=preview

ナレッツ、私は強く反対します。 コードを消費する開発者との間で、双方向バインディングが優先されるという契約が結ばれています。 バインディングのチェーンに沿って変更が加えられた場合は、続行する前にチェーン全体を更新する必要があります。 したがって、別のディレクティブがこのディレクティブを継承し、さらに何かがバインディングのバインディングにバインドされている場合でも...そのモデルが更新された場合、スタックの最下部にある元のコントローラースコープモデルは、前の同じダイジェストサイクルで更新される必要があります。他の機能に進みます。 それが消費者に期待されていることです。

少し関連していて、同様に混乱しています。 使用する場合はngChange共有することを別のフィールドにトリガーの検証にngModelの前に、変更がトリガされた$modelValue更新されます。 検証を複雑にします。

回避策として$timeoutを使用します。 なぜそれが必要なのかまだ混乱しています。

例:
http://codepen.io/jakobadam/pen/qmBdrJ?editors=1010

よろしく、ヤコブ

この問題を再検討する計画はありますか? おそらく再開しますか?

私はこの問題に遭遇し、コメントを読みましたが、分離スコープによる現在の動作方法は理にかなっています。 変更時の呼び出しが発生したときに更新されたモデルを期待し、モデルを1回前に取得することから生じる結果は、開発者として期待する動作ではありません。

私は質問に固執します、これは再訪されるつもりですか?

そうは思いません。 現在の動作は予想されており、望ましくない場合もありますが、少なくとも同じくらい多くの場合があります。 今すぐ変更すると、(a)重大な変更になり、(b)多くのユースケースがサポートされなくなります(たとえば、親を更新する前にngChange値を変更する場合)。 これは、すべての人を満足させることができないケースの1つであるため、次のバージョンを使用する必要があります。

  • 理にかなっています。
  • 既存のユーザーを壊しません。
  • 少し余分なコードが必要な場合でも、より具体的で高度なユースケースをサポートします(以下を参照)。

ngChangeの現在の実装は、 ngModel概念に密接に結びついています。 ディレクティブバインディングは完全に独立した概念です。 この2つはうまく連携できますが、相互の実装/内部動作に影響を与えることはありません。

すでに述べたように、特定のコールバックが更新された値にアクセスする必要がある場合、いくつかのオプションがあります。

  1. 更新された値を引数としてコールバックに渡します。

  2. 非同期で実行する必要がある場合(つまり、ダイジェストが終了し、すべてのバインディングが伝播された後)、コールバック内でいつでも$timeout使用できます。

  3. ngChange 「非同期」バージョンが必要な場合は、カスタムディレクティブとして実装するのも簡単です。 (組み込みのディレクティブを上書きすることもできますが、絶対にお勧めしません。)例:

.directive('myAsyncChange', function($timeout) {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, element, attr, ctrl) {
      ctrl.$viewChangeListeners.push(function() {
        $timeout(function() {
          scope.$eval(attr.myAsyncChange);
        });
      });
    }
  };
});
<input ng-model="foo" my-async-change="onChange()" />
このページは役に立ちましたか?
0 / 5 - 0 評価