DOMにAngularテンプレートがあります。私のコントローラがサービスから新しいデータを取得するとき、それは$スコープのモデルを更新し、そしてテンプレートを再レンダリングします。これまでのところすべて良かった。
問題は、テンプレートが再レンダリングされてDOMに入った後(この場合はjQueryプラグイン)にも追加の作業を行う必要があることです。
AfterRenderのような聴くべきイベントがあるはずですが、私はそのようなことを見つけることができません。たぶん指令が行く方法であるかもしれません、しかしそれはあまりにも早く発砲するように見えました。
これが私の問題の概要を説明するjsFiddleです。 Fiddle-AngularIssue
== UPDATE ==
有用なコメントに基づいて、私はそれに応じてDOM操作を処理するディレクティブに切り替え、ディレクティブの内側にモデル$ watchを実装しました。しかし、私はまだ同じ基本的な問題を抱えています。 $ watchイベント内のコードは、テンプレートがコンパイルされてDOMに挿入される前に発生するため、jqueryプラグインは常に空のテーブルを評価しています。
興味深いことに、非同期呼び出しを削除しても全体がうまく機能するので、正しい方向へのステップです。
これらの変更を反映するために私が更新したFiddleは次のとおりです。 http://jsfiddle.net/uNREn/12/
この記事は古いですが、私はあなたのコードを次のように変更します。
scope.$watch("assignments", function (value) {//I change here
var val = value || null;
if (val)
element.dataTable({"bDestroy": true});
});
}
jsfiddle を参照してください。
お役に立てば幸いです。
まず、レンダリングを台無しにするのに適切な場所はディレクティブです。私のアドバイスは、このようなディレクティブによってjQueryプラグインを操作するDOMをラップすることです。
私は同じ問題を抱えていて、この断片を思い付きました。 $watch
および$evalAsync
を使用して、ng-repeat
などのディレクティブが解決され、{{ value }}
などのテンプレートがレンダリングされた後にコードが実行されるようにします。
app.directive('name', function() {
return {
link: function($scope, element, attrs) {
// Trigger when number of children changes,
// including by directives like ng-repeat
var watch = $scope.$watch(function() {
return element.children().length;
}, function() {
// Wait for templates to render
$scope.$evalAsync(function() {
// Finally, directives are evaluated
// and templates are renderer here
var children = element.children();
console.log(children);
});
});
},
};
});
これがあなたが何らかの闘争を防ぐのを手伝うことができると思います。
Miskoのアドバイスに従い、もし非同期操作が欲しいのなら、$ timeout()の代わりに(これはうまくいかない)
$timeout(function () { $scope.assignmentsLoaded(data); }, 1000);
$ evalAsync()を使用します(これは動作します)。
$scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } );
フィドル 。また、データの変更が機能することを示すために、$ scope.assignmentsを変更してデータ/モデルへの変更をシミュレートする「データ行の削除」リンクも追加しました。
「概念の概要」ページの ランタイム のセクションでは、現在のスタックフレームの外側でブラウザがレンダリングする前に何か発生させる必要がある場合にevalAsyncを使用する必要があることを説明します。 (ここで推測しますが、「現在のスタックフレーム」にはおそらくAngular DOMの更新が含まれます。)ブラウザのレンダリング後に何か発生させる必要がある場合は$ timeoutを使用してください。
しかし、すでにご存知のように、ここでは非同期操作は必要ないと思います。
私は最も簡単な(安くて元気な)解決策は単にレンダリングされた最後の要素の最後にng-show = "someFunctionThatAlwaysReturnsZeroOrNothing()"で空のスパンを追加することです。この関数は、span要素を表示する必要があるかどうかを確認するときに実行されます。この関数の他のコードを実行してください。
私はこれが物事を行うための最もエレガントな方法ではないことを認識していますが、それは私のために動作します...
アニメーションが始まったときにローディングインジケータを削除する必要があったのとは少し逆になったものの、モバイルデバイスでは表示されるアニメーションよりもずっと早くAngularが初期化され、ローディングインジケータとしてNGクロークを使用するだけでは不十分でした。実際のデータが表示される前に削除されました。この場合、最初にレンダリングされた要素にreturn 0関数を追加し、その関数内でロードインジケータを非表示にするvarを反転しました。 (もちろん、この機能によって引き起こされるローディングインジケータにng-hideを追加しました。
私はあなたが$ evalAsync http://docs.angularjs.org/api/ng.$rootScope.Scope#$evalAsync を探していると思います
最後に私は解決策を見つけた、私は私のコレクションを更新するためにRESTサービスを使っていた。データ化可能なjqueryを変換するためのコードは以下のとおりです。
$scope.$watchCollection( 'conferences', function( old, nuew ) {
if( old === nuew ) return;
$( '#dataTablex' ).dataTable().fnDestroy();
$timeout(function () {
$( '#dataTablex' ).dataTable();
});
});
私はこれをかなり頻繁にしなければなりませんでした。私はディレクティブがあり、モデルのものが完全にDOMにロードされた後にいくつかのjqueryのものをする必要があります。だから私はリンクに私のロジックを入れて:ディレクティブの関数とsetTimeout(関数(){.....}、1)でコードをラップします。 setTimoutはDOMが読み込まれた後に起動します。1ミリ秒は、DOMが読み込まれてからコードが実行されるまでの最短時間です。これは私にはうまくいくようですが、そのテンプレートで使用されるディレクティブがjqueryものを実行してDOM要素にアクセスできるように、テンプレートのロードが完了したらAngularイベントを発生させたいです。お役に立てれば。
リンク関数でコードを実行するディレクティブを作成することもできます。
そのstackoverflow reply を参照してください。
$ scope。$ evalAsync()も$ timeout(fn、0)も私にとっては確実には機能しませんでした。
私は二つを組み合わせる必要がありました。指示を出し、また、デフォルト値よりも高い優先順位を設定しています。これがそのためのディレクティブです(注:依存関係を注入するためにngInjectを使用します)。
app.directive('postrenderAction', postrenderAction);
/* @ngInject */
function postrenderAction($timeout) {
// ### Directive Interface
// Defines base properties for the directive.
var directive = {
restrict: 'A',
priority: 101,
link: link
};
return directive;
// ### Link Function
// Provides functionality for the directive during the DOM building/data binding stage.
function link(scope, element, attrs) {
$timeout(function() {
scope.$evalAsync(attrs.postrenderAction);
}, 0);
}
}
指令を呼び出すには、次のようにします。
<div postrender-action="functionToRun()"></div>
Ng-repeatの実行が完了した後でそれを呼び出したい場合は、ng-repeatとng-if = "$ last"に空のスパンを追加しました。
<li ng-repeat="item in list">
<!-- Do stuff with list -->
...
<!-- Fire function after the last element is rendered -->
<span ng-if="$last" postrender-action="$ctrl.postRender()"></span>
</li>
私はかなり簡単な解決策を考え出しました。それが正しいやり方かどうかはわかりませんが、実用的な意味ではうまくいきます。レンダリングしたいものを直接見てみましょう。たとえば、いくつかのng-repeat
を含むディレクティブでは、段落のテキストの長さ(他のものもあります)に注意してください。指令は次のようになります。
.directive('myDirective', [function () {
'use strict';
return {
link: function (scope, element, attrs) {
scope.$watch(function(){
var whole_p_length = 0;
var ps = element.find('p');
for (var i=0;i<ps.length;i++){
if (ps[i].innerHTML == undefined){
continue
}
whole_p_length+= ps[i].innerHTML.length;
}
//it could be this too: whole_p_length = element[0].innerHTML.length; but my test showed that the above method is a bit faster
console.log(whole_p_length);
return whole_p_length;
}, function (value) {
//Code you want to be run after rendering changes
});
}
}]);
NOTEは、実際には レンダリングの変更後 または完全なレンダリング後にコードが実行されることを意味します。しかし、ほとんどの場合、レンダリングの変更が発生したときはいつでも状況に対処できると思います。また、コードの実行のみを 一度 レンダリングの完了後に実行したい場合は、このp
sの長さ(またはその他の測定値)をモデルと比較することも考えられます。 私はこれについての考え/コメントに感謝します。
サービスを更新して新しいビュー(ページ)にリダイレクトしてからサービスが更新される前にディレクティブが読み込まれるようなシナリオでは、$ rootScopeを使用できます。
見る
<service-history log="log" data-ng-repeat="log in requiedData"></service-history>
コントローラ
app.controller("MyController",['$scope','$rootScope', function($scope, $rootScope) {
$scope.$on('$viewContentLoaded', function () {
SomeSerive.getHistory().then(function(data) {
$scope.requiedData = data;
$rootScope.$broadcast("history-updation");
});
});
}]);
指令
app.directive("serviceHistory", function() {
return {
restrict: 'E',
replace: true,
scope: {
log: '='
},
link: function($scope, element, attrs) {
function updateHistory() {
if(log) {
//do something
}
}
$rootScope.$on("history-updation", updateHistory);
}
};
});
angular-ui utils の 'jQuery Passthrough'モジュールを使うことができます。私はjQuery touchのカルーセルプラグインをWebサービスから非同期で取得してng-repeatでレンダリングするいくつかの画像にうまくバインドできました。