AngularJS でのダーティチェック
AngularJs でダーティチェックを行う方法を紹介します。
AngularJS にダーティチェックを実装する
AngularJS は、双方向データバインディングの $scope
変数に対してダーティチェックを実行します。Ember.js
は、セッターとゲッターをプログラムで修正することで双方向のデータバインディングを実行しますが、Angular では、ダーティチェックにより、使用可能かどうかに関係なく変数を検索できます。
# AngularJs
$scope.$watch( wExp, listener, objEq );
$scope.$watch
関数は、変数がいつ変更されたかを確認したい場合に使用されます。この関数を適用するには、3つの引数を選択する必要があります。wExp
は監視したいもの、listener
は更新時に実行したいこと、変数とオブジェクトのどちらをチェックするかです。
変数をチェックしたいときにこの関数を使用している間はこれをスキップできます。以下に示す例を見てみましょう。
#AngularJs
$scope.newName = 'Subhan';
$scope.$watch( function( ) {
return $scope.name;
}, function( newVal, oldVal ) {
console.log('$scope.newName is updated!');
} );
Angular は、ウォッチャー機能を $scope
に保存します。これは、$scope
をコンソールに記録することで確認できます。
$watch
では、関数の代わりに文字列を利用することもできます。これも関数が機能するのと同じように機能します。
Angular ソースコードに文字列を追加すると、コードは次のようになります。
#AngularJs
if (typeof wExp == 'string' && get.constant) {
var newFn = watcher.fn;
watcher.fn = function(newVal, oldVal, scope) {
newFn.call(this, newVal, oldVal, scope);
arrayRemove(array, watcher);
};
}
このコードを適用することにより、wExp
が関数として設定されます。これにより、選択した名前の変数でリスナーが指定されます。
AngularJS の $watchers
関数
選択したすべてのウォッチャーは、$scope
の $$watchers
変数に保存されます。以下に示すように、$$watchers
をチェックすると、オブジェクトの配列が見つかります。
#AngularJs
$$watchers = [
{
eq: false,
fn: function( newVal, oldVal ) {},
last: 'Subhan',
exp: function(){},
get: function(){}
}
];
unRegWatch
関数は $watch
関数によって返されます。これは、最初の $scope.$watch
を変数に割り当てたい場合、監視を停止するように命令することで簡単にこれを達成できることを示しています。
ウォッチャーを切断する前に、ログに記録された最初の $scope
を開いて確認したことを確認してください。
AngularJS の $scope.$apply
関数
controller/directive/etc.
を実行しようとすると、Angular は $scope.$watch
を呼び出す関数を実行します。rootScope
で $digest
関数を管理する前に、$apply
関数は選択した関数を実行します。
Angular の $apply
関数は次のとおりです。
#AngularJs
$apply: function(expr) {
try {
beginPhase('$apply');
return this.$eval(expr);
} catch (e) {
$exceptionHandler(e);
} finally {
clearPhase();
try {
$rootScope.$digest();
} catch (e) {
$exceptionHandler(e);
throw e;
}
}
}
AngularJS の expr
引数
$scope.$apply
を使用する場合、場合によっては Angular を使用する場合、または関数を渡す場合、expr
引数はその関数です。
この機能を使用している間、$apply
を最も頻繁に使用する必要はありません。ng-keydown
が $scope.$apply
を使用するとどうなるか見てみましょう。
ディレクティブを送信するには、次のコードを使用します。
#AngularJs
var EventNgDirective = {};
forEach(
keydown keyup keypress submit focus blur copy cut paste'.split(' '),
function(newName) {
var newDirectiveName = newDirectiveNormalize('ng-' + newName);
EventNgDirective[newDirectiveName] = ['$parse', function($parse) {
return {
compile: function($element, attr) {
var fn = $parse(attr[directiveName]);
return function ngEventHandler(scope, element) {
element.on(lowercase(newName), function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
});
});
};
}
};
}];
}
);
このコードは、発生する可能性のある対照的なタイプのイベントをループし、ng(eventName)
と呼ばれる新しいディレクティブを生成します。ディレクティブのコンパイル機能が関係する場合、イベントハンドラーが要素に記録され、イベントにはディレクティブの名前が含まれます。
このイベントが発生すると、$scope.$apply
が Angular によって実行され、実行する機能が提供されます。
このようにして、$scope
値が要素値で更新されますが、このバインディングは一方向のバインディングにすぎません。この理由は、ng-keydown
を適用したためです。これにより、このイベントが適用された場合にのみ変更できます。
その結果、新しい値が得られます!
私たちの主な目標は、双方向のデータバインディングを実現することです。この目標を達成するために、ng-model
を使用できます。
ng-model
が機能するために、$scope.$watch
と $scope.$apply
の両方を利用します。
私たちが与えた入力は、ng-model
によってイベントハンドラーに参加します。この時点で、$scope.$watch
が呼び出されます。
$scope.$watch
は、ディレクティブのコントローラーで呼び出されます。
#AngularJs
$scope.$watch(function ngModelWatch() {
var value = ngModelGet($scope);
if (ctrl.$modelValue !== value) {
var formatters = ctrl.$formatters,
idx = formatters.length;
ctrl.$modelValue = value;
while(idx--) {
value = formatters[idx](value);
}
if (ctrl.$viewValue !== value) {
ctrl.$viewValue = value;
ctrl.$render();
}
}
return value;
});
$scope.$watch
の設定時に引数を 1つだけ使用すると、更新されているかどうかに関係なく、選択した関数が適用されます。ng-model
にある関数は、モデルとビューが同期しているかどうかを調べます。
同期していない場合、関数はモデルに新しい値を与えて更新します。この関数を使用すると、$digest
でこの関数を実行したときに新しい値を返すことで、新しい値を知ることができます。
リスナーが解雇されない理由
説明したのと同様の関数で $scope.$watch
関数の登録を解除した例のポイントに戻りましょう。 $scope.name
を更新しても通知が届かない理由を確認できるようになりました。
ご存知のとおり、$scope.$apply
は、各ディレクティブコントローラ関数で Angular によって実行されます。 $scope.$apply
関数は、ディレクティブのコントローラー関数を推定した 1つの条件でのみ $digest
を実行します。
これは、実行できるようになる前に $scope.$watch
関数の登録を解除したことを意味します。したがって、呼び出される可能性は最小限またはまったくありません。
AngularJS の $digest
関数
この関数は、$scope.$apply
によって $rootScope
で呼び出すことができます。 $rootScope
でダイジェストサイクルを実行すると、スコープを通過し、代わりにダイジェストサイクルを実行します。
ダイジェストサイクルは、$$watchers
変数のすべての wExp
関数を起動します。最後の既知の値に対してそれらをチェックします。
結果が否定的な場合、リスナーは解雇されます。
ダイジェストサイクルでは、実行時に 2 回ループします。一度、ウォッチャー間でループし、サイクルがダーティ
でなくなるまで再びループします。
wExp
と最後の既知の値が同等でない場合、サイクルがダーティであると言います。通常、このサイクルは 1 回実行され、10 回を超えるとエラーが発生します。
Angular は、モデル値が変更される可能性のあるすべてのものに対して $scope.$apply
を実行できます。
$scope.$apply();
を実行する必要があります Angular の外で $scope
を更新したとき。これにより、スコープが更新されたことが Angular に通知されます。
ダーティチェックの基本バージョンを設計しましょう。
保存したいすべてのデータは、Scope
機能に含まれます。関数のオブジェクトを展開して、$digest
と $watch
を複製します。
Scope
に関連する機能を評価する必要がないため、$apply
は使用しません。 $digest
を直接使用します。
Scope
は次のようになります。
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( ) {
};
Scope.prototype.$digest = function( ) {
};
$watch
が採用する必要がある 2つのパラメータ、wExp
と listener
があります。Scope
で値を設定できます。
$watch
が呼び出されると、これらは以前に Scope
に保存されていた $$watcher
値に送信されます。
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
};
ここで、listener
が空の関数に設定されていることがわかります。
この例に従うことで、listener
が提供されていない場合、すべての変数に $watch
を簡単に登録できます。
次に、$digest
を操作します。古い値と新しい値が同等であるかどうかを調べることができます。
リスナーがまだ起動されていない場合は、リスナーを起動することもできます。これを実現するために、同等になるまでループします。
dirtyChecking
変数は、値が等しいかどうかに関係なく、この目標を達成するのに役立ちました。
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
var dirtyChecking;
do {
dirtyChecking = false;
for( var i = 0; i < this.$$watchers.length; i++ ) {
var newVal = this.$$watchers[i].wExp(),
oldVal = this.$$watchers[i].last;
if( oldVal !== newVal ) {
this.$$watchers[i].listener(newVal, oldVal);
dirtyChecking = true;
this.$$watchers[i].last = newVal;
}
}
} while(dirtyChecking);
};
次に、スコープの新しい例を設計します。これを $scope
に割り当てます。次に、監視機能を登録します。
次に、それを更新してダイジェストします。
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
var dirtyChecking;
do {
dirtyChecking = false;
for( var i = 0; i < this.$$watchers.length; i++ ) {
var newVal = this.$$watchers[i].wExp(),
oldVal = this.$$watchers[i].last;
if( oldVal !== newVal ) {
this.$$watchers[i].listener(newVal, oldVal);
dirtyChecking = true;
this.$$watchers[i].last = newVal;
}
}
} while(dirtyChecking);
};
var $scope = new Scope();
$scope.newName = 'Subhan';
$scope.$watch(function(){
return $scope.newName;
}, function( newVal, oldVal ) {
console.log(newVal, oldVal);
} );
$scope.$digest();
ダーティチェックの実装に成功しました。コンソールを見ると、ログが表示されます。
#AngularJs
Subhan undefined
以前は、$scope.newName
は定義されていませんでした。Subhan
用に修正しました。
$digest
関数を入力の keyup
イベントに接続します。これにより、自分で指定する必要がなくなります。
これは、双方向のデータバインディングも実現できることを意味します。
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
var dirty;
do {
dirtyChecking = false;
for( var i = 0; i < this.$$watchers.length; i++ ) {
var newVal = this.$$watchers[i].wExp(),
oldVal = this.$$watchers[i].last;
if( oldVal !== newVal ) {
this.$$watchers[i].listener(newVal, oldVal);
dirtyChecking = true;
this.$$watchers[i].last = newVal;
}
}
} while(dirtyChecking);
};
var $scope = new Scope();
$scope.newName = 'Subhan';
var element = document.querySelectorAll('input');
element[0].onkeyup = function() {
$scope.newName = element[0].value;
$scope.$digest();
};
$scope.$watch(function(){
return $scope.newName;
}, function( newVal, oldVal ) {
console.log('Value updated In Input - The new value is ' + newVal);
element[0].value = $scope.newName;
} );
var updateScopeValue = function updateScopeValue( ) {
$scope.name = 'Butt';
$scope.$digest();
};
この手法を使用すると、$scope.newName
に示されているように、入力の値を簡単に更新できます。updateScopeValue
を呼び出すこともでき、入力の値はそれを示します。
Rana is a computer science graduate passionate about helping people to build and diagnose scalable web application problems and problems developers face across the full-stack.
LinkedIn