私が実装しようとしているのは、基本的に "on ng repeat finished rendering"ハンドラです。いつ完了したのかを検出することはできますが、そこから関数をトリガーする方法を理解することはできません。
フィドルをチェックしてください。 http://jsfiddle.net/paulocoelho/BsMqq/3/
JS
var module = angular.module('testApp', [])
.directive('onFinishRender', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
element.ready(function () {
console.log("calling:"+attr.onFinishRender);
// CALL TEST HERE!
});
}
}
}
});
function myC($scope) {
$scope.ta = [1, 2, 3, 4, 5, 6];
function test() {
console.log("test executed");
}
}
HTML
<div ng-app="testApp" ng-controller="myC">
<p ng-repeat="t in ta" on-finish-render="test()">{{t}}</p>
</div>
答え:finishmoveからの作業フィドル: http://jsfiddle.net/paulocoelho/BsMqq/4/
var module = angular.module('testApp', [])
.directive('onFinishRender', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit(attr.onFinishRender);
});
}
}
}
});
私は.ready()
を使用せず、むしろ$timeout
でラップしたことに注意してください。 $timeout
は、ng-繰り返された要素が本当にレンダリングを終了したときに実行されることを確実にします($timeout
は現在のダイジェストサイクルの終わりに実行されるので - そしてsetTimeout
とは異なり内部的に$apply
も呼び出します) )そのため、ng-repeat
が終了したら、$emit
を使用して外部スコープ(兄弟および親スコープ)にイベントを発行します。
そして、あなたのコントローラで、あなたは$on
でそれをキャッチすることができます:
$scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) {
//you also get the actual event object
//do stuff, execute functions -- whatever...
});
このHTMLのようになります。
<div ng-repeat="item in items" on-finish-render="ngRepeatFinished">
<div>{{item.name}}}<div>
</div>
DOMが構築された後、ブラウザがレンダリングされる前にコールバック(つまりtest())を実行したい場合は $ evalAsync を使用します。これはちらつきを防ぐでしょう - ref 。
if (scope.$last) {
scope.$evalAsync(attr.onFinishRender);
}
フィドル 。
レンダリング後にコールバックを本当に呼び出したい場合は、$ timeoutを使用してください。
if (scope.$last) {
$timeout(function() {
scope.$eval(attr.onFinishRender);
});
}
私はイベントよりも$ evalを好む。イベントでは、イベントの名前を知り、そのイベントのコントローラにコードを追加する必要があります。 $ evalを使用すると、コントローラとディレクティブの間のカップリングが少なくなります。
これまでに与えられた回答は、ng-repeat
が初めてレンダリングされるときのみ機能します、ただし、動的なng-repeat
がある場合は、アイテムを追加/削除/フィルタリングし、ng-repeat
がレンダリングされるたびに通知する必要がある場合、これらのソリューションは機能しません。
したがって、ng-repeat
が再レンダリングされるたびに通知する必要がある場合初めてではなく、それを行う方法を見つけましたが、それはかなり「ハック」です、しかし、あなたが何をしているのかを知っていれば、うまくいきます。 $filter
でこのng-repeat
を使用します他の$filter
を使用する前に:
.filter('ngRepeatFinish', function($timeout){
return function(data){
var me = this;
var flagProperty = '__finishedRendering__';
if(!data[flagProperty]){
Object.defineProperty(
data,
flagProperty,
{enumerable:false, configurable:true, writable: false, value:{}});
$timeout(function(){
delete data[flagProperty];
me.$emit('ngRepeatFinished');
},0,false);
}
return data;
};
})
これにより、$emit
がレンダリングされるたびにng-repeat
というイベントがngRepeatFinished
と呼ばれます。
<li ng-repeat="item in (items|ngRepeatFinish) | filter:{name:namedFiltered}" >
ngRepeatFinish
フィルターは、$scope
で定義されているArray
またはObject
に直接適用する必要があります。その後、他のフィルターを適用できます。
<li ng-repeat="item in (items | filter:{name:namedFiltered}) | ngRepeatFinish" >
最初に他のフィルターを適用してからngRepeatFinish
フィルターを適用しないでください。
ng-repeat
によって再レンダリングされたDOM要素の新しいディメンションを考慮する必要があるため、リストのレンダリングが完了した後に特定のcssスタイルをDOMに適用する場合。 (ところで:これらの種類の操作はディレクティブ内で実行する必要があります)
ngRepeatFinished
イベントを処理する関数ですべきでないこと:その関数で$scope.$apply
を実行しないでください。実行すると、Angularが検出できない無限ループにAngularを配置します。
$scope
プロパティの変更には使用しないでください。これらの変更は、次の$digest
ループまで、および$scope.$apply
を実行できないため、ビューに反映されません。それらは役に立たないでしょう。
いいえ、彼らはそうではありません、これがハックです、あなたがそれを好まなければそれを使用しないでください。同じことを達成するためのより良い方法を知っているなら、私にそれを知らせてください。
これはハックです、間違った方法で使用するのは危険です。
ng-repeat
のレンダリングが完了して問題が発生しないようにスタイルを適用する場合にのみ使用してください。
同じコントローラ上で異なるng-repeatsに対して異なる関数を呼び出す必要がある場合は、次のようにして試すことができます。
指令:
var module = angular.module('testApp', [])
.directive('onFinishRender', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit(attr.broadcasteventname ? attr.broadcasteventname : 'ngRepeatFinished');
});
}
}
}
});
あなたのコントローラーで、$ onのイベントをキャッチしてください:
$scope.$on('ngRepeatBroadcast1', function(ngRepeatFinishedEvent) {
// Do something
});
$scope.$on('ngRepeatBroadcast2', function(ngRepeatFinishedEvent) {
// Do something
});
複数のng-repeatを含むテンプレート内
<div ng-repeat="item in collection1" on-finish-render broadcasteventname="ngRepeatBroadcast1">
<div>{{item.name}}}<div>
</div>
<div ng-repeat="item in collection2" on-finish-render broadcasteventname="ngRepeatBroadcast2">
<div>{{item.name}}}<div>
</div>
他の解決策は最初のページロードでうまくいきますが、モデルが変更されたときにあなたの関数が呼び出されることを保証する唯一の方法はコントローラから$ timeoutを呼び出すことです。これは$ timeoutを使う実用的な フィドル です。あなたの例ではそれは次のようになります。
.controller('myC', function ($scope, $timeout) {
$scope.$watch("ta", function (newValue, oldValue) {
$timeout(function () {
test();
});
});
ngRepeatは行の内容が新しい場合にのみディレクティブを評価するので、リストから項目を削除してもonFinishRenderは起動しません。たとえば、これらのフィドル emit にフィルタ値を入力してみます。
2ドルのスコープスコープを使わずに、内容が繰り返しのみのディレクティブを書いているのであれば、かなり簡単な解決策があります(最初のレンダリングだけを考えていると仮定して)。リンク機能では:
const dereg = scope.$watch('$$childTail.$last', last => {
if (last) {
dereg();
// do yr stuff -- you may still need a $timeout here
}
});
これはレンダリングされたリストのメンバーの幅や高さに基づいてDOMを操作する必要があるディレクティブがある場合に役立ちますが、これは一般的ではありません。提案されている他の解決策として。
とても簡単です、これが私のやり方です。
.directive('blockOnRender', function ($blockUI) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
if (scope.$first) {
$blockUI.blockElement($(element).parent());
}
if (scope.$last) {
$blockUI.unblockElement($(element).parent());
}
}
};
})
フィルタ処理されたngRepeatを使ったこの問題の解決策は、 突然変異 イベントを使ったことですが、これらは推奨されません(すぐに置き換えられない)。
それから私は別の簡単なものを考えました:
app.directive('filtered',function($timeout) {
return {
restrict: 'A',link: function (scope,element,attr) {
var Elm = element[0]
,nodePrototype = Node.prototype
,timeout
,slice = Array.prototype.slice
;
Elm.insertBefore = alt.bind(null,nodePrototype.insertBefore);
Elm.removeChild = alt.bind(null,nodePrototype.removeChild);
function alt(fn){
fn.apply(Elm,slice.call(arguments,1));
timeout&&$timeout.cancel(timeout);
timeout = $timeout(altDone);
}
function altDone(){
timeout = null;
console.log('Filtered! ...fire an event or something');
}
}
};
});
これは親要素のNode.prototypeメソッドにワンタイムの$ timeoutでフックして、連続した変更を監視します。
それは大体正しく働きますが、私はaltDoneが二度呼ばれるといういくつかのケースを得ました。
このディレクティブをngRepeatの親に追加します。
私はこの質問に対する答えの中で最も単純な解決策を見ないことにとても驚いています。あなたがしたいことは、繰り返し要素(ngInit
ディレクティブを持つ要素)にngRepeat
ディレクティブを追加して$last
(ngRepeat
によってスコープ内に設定された特別な変数)をチェックすることです。これは繰り返し要素がリストの最後であることを示します。 $last
がtrueの場合、最後の要素をレンダリングしているので、必要な関数を呼び出すことができます。
ng-init="$last && test()"
HTMLマークアップの完全なコードは次のようになります。
<div ng-app="testApp" ng-controller="myC">
<p ng-repeat="t in ta" ng-init="$last && test()">{{t}}</p>
</div>
test
はAngular.jsによって提供されるので、呼び出すスコープ関数(この場合はngInit
)以外に、アプリケーションに特別なJSコードは必要ありません。テンプレートからアクセスできるように、test
関数がスコープ内にあることを確認してください。
$scope.test = function test() {
console.log("test executed");
}
フィドルを見てください http://jsfiddle.net/yNXS2/ 。あなたが作成したディレクティブは新しいスコープを作成しなかったので、私は邪魔をしませんでした。
$scope.test = function(){...
はそれを実現しました。