回答:
ただの直感:ngCloakディレクティブがどのようにそれを行うのか見てみませんか?明らかに、ngCloakディレクティブは、物がロードされた後にコンテンツを表示するように管理します。私はngCloakを見ることは正確な答えにつながるでしょう...
1時間後の編集:わかり ました。まあ、ngCloakを調べたところ、本当に短いのです。これが明らかに意味するのは、{{template}}式が評価されるまで(つまり、読み込まれたテンプレート)、ngCloakディレクティブの優れた機能になるまで、コンパイル関数が実行されないことです。
私の教育を受けた推測は、ngCloakと同じ単純さでディレクティブを作成し、コンパイル関数で何でもしたいことを行うことです。:)ディレクティブをアプリのルート要素に配置します。myOnloadのようなディレクティブを呼び出して、属性my-onloadとして使用できます。テンプレートがコンパイルされると、コンパイル関数が実行されます(式が評価され、サブテンプレートが読み込まれます)。
編集、23時間後: わかりました、それで私はいくつかの研究をしました、そして私は私自身の質問もしました。私が尋ねた質問は、この質問に間接的に関連していましたが、偶然にもこの質問を解決する答えに導いてくれました。
答えは、単純なディレクティブを作成し、コードをディレクティブのリンク関数に配置できることです。これは、要素の準備ができている/読み込まれたときに実行されます(ほとんどのユースケースでは、以下で説明します)。コンパイルおよびリンク関数が実行される順序のジョシュの記述に基づいて、
このマークアップがある場合:
<div directive1> <div directive2> <!-- ... --> </div> </div>
次に、AngularJSはディレクティブ関数を特定の順序で実行してディレクティブを作成します。
directive1: compile directive2: compile directive1: controller directive1: pre-link directive2: controller directive2: pre-link directive2: post-link directive1: post-link
デフォルトでは、まっすぐな「リンク」関数はポストリンクなので、外側のdirective1のリンク関数は、内側のdirective2のリンク関数が実行されるまで実行されません。これが、ポストリンクでDOM操作を行うことが安全であると私たちが言う理由です。したがって、元の質問に向かって、動的に挿入されたコンテンツは上記のようにコンパイルする必要がありますが、外部ディレクティブのリンク関数から子ディレクティブの内部htmlにアクセスしても問題はありません。
これから、すべてが準備/コンパイル/リンク/ロードされたときにコードを実行するディレクティブを作成するだけでよいと結論付けることができます。
app.directive('ngElementReady', [function() {
return {
priority: -1000, // a low number so this directive loads after all other directives have loaded.
restrict: "A", // attribute only
link: function($scope, $element, $attributes) {
console.log(" -- Element ready!");
// do what you want here.
}
};
}]);
ここでできることは、ngElementReadyディレクティブをアプリのルート要素に配置し、console.log
ロード時にが起動することです。
<body data-ng-app="MyApp" data-ng-element-ready="">
...
...
</body>
とても簡単です!単純なディレクティブを作成して使用するだけです。;)
さらにカスタマイズして、式(つまり関数)を実行できるように追加することもでき$scope.$eval($attributes.ngElementReady);
ます。
app.directive('ngElementReady', [function() {
return {
priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any.
restrict: "A",
link: function($scope, $element, $attributes) {
$scope.$eval($attributes.ngElementReady); // execute the expression in the attribute.
}
};
}]);
その後、任意の要素で使用できます。
<body data-ng-app="MyApp" data-ng-controller="BodyCtrl" data-ng-element-ready="bodyIsReady()">
...
<div data-ng-element-ready="divIsReady()">...<div>
</body>
要素が存在するスコープ(コントローラー内)で関数(bodyIsReadyやdivIsReadyなど)が定義されていることを確認してください。
警告:これはほとんどの場合に機能すると述べました。ngRepeatやngIfなどの特定のディレクティブを使用するときは注意してください。それらは独自のスコープを作成し、ディレクティブは起動しない可能性があります。たとえば、ngIfもある要素に新しいngElementReadyディレクティブを配置し、ngIfの条件がfalseと評価された場合、ngElementReadyディレクティブは読み込まれません。または、たとえば、ngIncludeディレクティブもある要素に新しいngElementReadyディレクティブを配置した場合、ngIncludeのテンプレートが存在しない場合、ディレクティブは読み込まれません。ディレクティブをすべて同じ要素に置くのではなく、ネストすることで、これらの問題のいくつかを回避できます。たとえば、次のようにします。
<div data-ng-element-ready="divIsReady()">
<div data-ng-include="non-existent-template.html"></div>
<div>
これの代わりに:
<div data-ng-element-ready="divIsReady()" data-ng-include="non-existent-template.html"></div>
ngElementReadyディレクティブは後者の例でコンパイルされますが、そのリンク関数は実行されません。注:ディレクティブは常にコンパイルされますが、上記のような特定のシナリオによっては、リンク関数が常に実行されるとは限りません。
編集、数分後:
ああ、そして質問に完全に答えるために、あなたは今、$emit
または$broadcast
あなたのイベントを、ng-element-ready
属性で実行される式または関数からすることができます。:)例:
<div data-ng-element-ready="$emit('someEvent')">
...
<div>
編集、さらに数分後:
@satchmorunの答えも機能しますが、初期ロードの場合のみです。以下は、リンク関数、などを含む、実行される順序を説明する非常に便利なSOの質問ですapp.run
。したがって、ユースケースによっては、app.run
適切な場合がありますが、特定の要素には適していません。その場合、リンク関数の方が適しています。
編集、5か月後、10月17日8:11 PST:
これは、非同期に読み込まれるパーシャルでは機能しません。パーシャルに簿記を追加する必要があります(たとえば、コンテンツの読み込みが完了したときに各パーシャルを追跡し、イベントを発行して、親スコープが読み込まれたパーシャルの数をカウントし、最終的に必要なことを実行できるようにする方法があります。すべてのパーシャルがロードされた後で行います)。
編集、10月23日午後10:52 PST:
画像が読み込まれたときにいくつかのコードを起動するための簡単なディレクティブを作成しました。
/*
* This img directive makes it so that if you put a loaded="" attribute on any
* img element in your app, the expression of that attribute will be evaluated
* after the images has finished loading. Use this to, for example, remove
* loading animations after images have finished loading.
*/
app.directive('img', function() {
return {
restrict: 'E',
link: function($scope, $element, $attributes) {
$element.bind('load', function() {
if ($attributes.loaded) {
$scope.$eval($attributes.loaded);
}
});
}
};
});
編集、10月24日午前12:48 PST:
元のngElementReady
ディレクティブを改善し、名前をに変更しましたwhenReady
。
/*
* The whenReady directive allows you to execute the content of a when-ready
* attribute after the element is ready (i.e. done loading all sub directives and DOM
* content except for things that load asynchronously like partials and images).
*
* Execute multiple expressions by delimiting them with a semi-colon. If there
* is more than one expression, and the last expression evaluates to true, then
* all expressions prior will be evaluated after all text nodes in the element
* have been interpolated (i.e. {{placeholders}} replaced with actual values).
*
* Caveats: if other directives exists on the same element as this directive
* and destroy the element thus preventing other directives from loading, using
* this directive won't work. The optimal way to use this is to put this
* directive on an outer element.
*/
app.directive('whenReady', ['$interpolate', function($interpolate) {
return {
restrict: 'A',
priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any.
link: function($scope, $element, $attributes) {
var expressions = $attributes.whenReady.split(';');
var waitForInterpolation = false;
function evalExpressions(expressions) {
expressions.forEach(function(expression) {
$scope.$eval(expression);
});
}
if ($attributes.whenReady.trim().length == 0) { return; }
if (expressions.length > 1) {
if ($scope.$eval(expressions.pop())) {
waitForInterpolation = true;
}
}
if (waitForInterpolation) {
requestAnimationFrame(function checkIfInterpolated() {
if ($element.text().indexOf($interpolate.startSymbol()) >= 0) { // if the text still has {{placeholders}}
requestAnimationFrame(checkIfInterpolated);
}
else {
evalExpressions(expressions);
}
});
}
else {
evalExpressions(expressions);
}
}
}
}]);
たとえば、次のように使用してsomeFunction
、要素が読み込まれ、{{placeholders}}
まだ置換されていないときに起動します。
<div when-ready="someFunction()">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
すべてのitem.property
プレースホルダーが置き換えられる前に呼び出されます。
必要なだけ式true
を{{placeholders}}
評価し、次のように評価されるのを待つ最後の式を作成します。
<div when-ready="someFunction(); anotherFunction(); true">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
そしてanotherFunction
後に解雇されます{{placeholders}}
置き換えられています。
これは要素が最初にロードされたときにのみ機能し、将来の変更では機能しません。$digest
プレースホルダーが最初に置き換えられた後もキープが継続する場合は、期待どおりに機能しない可能性があります($ digestは、データの変更が停止するまで最大10回発生する可能性があります)。ほとんどのユースケースに適しています。
編集、10月31日午後7:26 PST:
了解しました。おそらくこれが最後で最後の更新です。これはおそらく、99.999のユースケースで機能します。
/*
* The whenReady directive allows you to execute the content of a when-ready
* attribute after the element is ready (i.e. when it's done loading all sub directives and DOM
* content). See: /programming/14968690/sending-event-when-angular-js-finished-loading
*
* Execute multiple expressions in the when-ready attribute by delimiting them
* with a semi-colon. when-ready="doThis(); doThat()"
*
* Optional: If the value of a wait-for-interpolation attribute on the
* element evaluates to true, then the expressions in when-ready will be
* evaluated after all text nodes in the element have been interpolated (i.e.
* {{placeholders}} have been replaced with actual values).
*
* Optional: Use a ready-check attribute to write an expression that
* specifies what condition is true at any given moment in time when the
* element is ready. The expression will be evaluated repeatedly until the
* condition is finally true. The expression is executed with
* requestAnimationFrame so that it fires at a moment when it is least likely
* to block rendering of the page.
*
* If wait-for-interpolation and ready-check are both supplied, then the
* when-ready expressions will fire after interpolation is done *and* after
* the ready-check condition evaluates to true.
*
* Caveats: if other directives exists on the same element as this directive
* and destroy the element thus preventing other directives from loading, using
* this directive won't work. The optimal way to use this is to put this
* directive on an outer element.
*/
app.directive('whenReady', ['$interpolate', function($interpolate) {
return {
restrict: 'A',
priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any.
link: function($scope, $element, $attributes) {
var expressions = $attributes.whenReady.split(';');
var waitForInterpolation = false;
var hasReadyCheckExpression = false;
function evalExpressions(expressions) {
expressions.forEach(function(expression) {
$scope.$eval(expression);
});
}
if ($attributes.whenReady.trim().length === 0) { return; }
if ($attributes.waitForInterpolation && $scope.$eval($attributes.waitForInterpolation)) {
waitForInterpolation = true;
}
if ($attributes.readyCheck) {
hasReadyCheckExpression = true;
}
if (waitForInterpolation || hasReadyCheckExpression) {
requestAnimationFrame(function checkIfReady() {
var isInterpolated = false;
var isReadyCheckTrue = false;
if (waitForInterpolation && $element.text().indexOf($interpolate.startSymbol()) >= 0) { // if the text still has {{placeholders}}
isInterpolated = false;
}
else {
isInterpolated = true;
}
if (hasReadyCheckExpression && !$scope.$eval($attributes.readyCheck)) { // if the ready check expression returns false
isReadyCheckTrue = false;
}
else {
isReadyCheckTrue = true;
}
if (isInterpolated && isReadyCheckTrue) { evalExpressions(expressions); }
else { requestAnimationFrame(checkIfReady); }
});
}
else {
evalExpressions(expressions);
}
}
};
}]);
このように使う
<div when-ready="isReady()" ready-check="checkIfReady()" wait-for-interpolation="true">
isReady will fire when this {{placeholder}} has been evaluated
and when checkIfReady finally returns true. checkIfReady might
contain code like `$('.some-element').length`.
</div>
もちろん、おそらく最適化できますが、それはそのままにしておきます。requestAnimationFrameは素晴らしいです。
のドキュメントにはangular.Module
、run
関数を説明するエントリがあります。
このメソッドを使用して、インジェクターがすべてのモジュールのロードを完了したときに実行する必要がある作業を登録します。
したがって、アプリであるモジュールがある場合:
var app = angular.module('app', [/* module dependencies */]);
モジュールが読み込まれた後、次のものを実行できます:
app.run(function() {
// Do post-load initialization stuff here
});
そのためrun
、DOMが準備されリンクされたときにが呼び出されないことが指摘されています。$injector
によって参照されるモジュールのng-app
がすべての依存関係をロードしたときに呼び出されます。これは、DOMコンパイル手順とは別です。
手動での初期化をもう一度確認しましたが、これでうまくいくようです。
HTMLは単純です。
<html>
<body>
<test-directive>This is a test</test-directive>
</body>
</html>
の欠如に注意してください ng-app
。そして、DOM操作を行うディレクティブがあるので、物事の順序とタイミングを確認できます。
いつものように、モジュールが作成されます:
var app = angular.module('app', []);
そしてここにディレクティブがあります:
app.directive('testDirective', function() {
return {
restrict: 'E',
template: '<div class="test-directive"><h1><div ng-transclude></div></h1></div>',
replace: true,
transclude: true,
compile: function() {
console.log("Compiling test-directive");
return {
pre: function() { console.log("Prelink"); },
post: function() { console.log("Postlink"); }
};
}
};
});
test-directive
タグをdiv
クラスtest-directive
ので置き換え、そのコンテンツをでラップしh1
ます。
これらのものがいつ実行されるかを確認できるように、リンク前関数とリンク後関数の両方を返すコンパイル関数を追加しました。
残りのコードは次のとおりです。
// The bootstrapping process
var body = document.getElementsByTagName('body')[0];
// Check that our directive hasn't been compiled
function howmany(classname) {
return document.getElementsByClassName(classname).length;
}
何かを行う前はtest-directive
、DOMにのクラスを持つ要素はなく、完了後は1になるはずです。
console.log('before (should be 0):', howmany('test-directive'));
angular.element(document).ready(function() {
// Bootstrap the body, which loades the specified modules
// and compiled the DOM.
angular.bootstrap(body, ['app']);
// Our app is loaded and the DOM is compiled
console.log('after (should be 1):', howmany('test-directive'));
});
とても簡単です。ドキュメントの準備ができたら、angular.bootstrap
アプリのルート要素とモジュール名の配列を使用して呼び出します。
実際、モジュールに関数をアタッチするとrun
app
にと、コンパイルが行われる前に実行されることがわかります。
フィドルを実行してコンソールを見ると、次のように表示されます。
before (should be 0): 0
Compiling test-directive
Prelink
Postlink
after (should be 1): 1 <--- success!
run
前に起動し、起動時にHTMLがすべてではありません
$timeout( initMyPlugins,0)
私のディレクティブ内の作品を使用して、必要なすべてのhtmlがそこにあることを発見しました
角度の初期化がいつ完了したかを評価するのに比較的正確な解決策を考え出しました。
ディレクティブは次のとおりです。
.directive('initialisation',['$rootScope',function($rootScope) {
return {
restrict: 'A',
link: function($scope) {
var to;
var listener = $scope.$watch(function() {
clearTimeout(to);
to = setTimeout(function () {
console.log('initialised');
listener();
$rootScope.$broadcast('initialised');
}, 50);
});
}
};
}]);
次に、属性としてbody
要素に追加し、使用するためにリッスンすることができます$scope.$on('initialised', fn)
これは、$ digestサイクルがなくなるとアプリケーションが初期化されると想定して機能します。$ watchはすべてのダイジェストサイクルで呼び出されるため、タイマーが開始されます(setTimeoutは$ timeoutではなく、新しいダイジェストサイクルはトリガーされません)。タイムアウト内にダイジェストサイクルが発生しない場合、アプリケーションは初期化されていると見なされます。
satchmorunsソリューションほど正確ではないことは明らかですが(ダイジェストサイクルがタイムアウトよりも長くなる可能性があるため)、私のソリューションではモジュールを追跡する必要がないため、管理が非常に簡単になります(特に大規模なプロジェクトの場合)。 )。とにかく、私の要件には十分正確であるようです。それが役に立てば幸い。
Angular UIルーターを使用している場合は、$viewContentLoaded
イベントをリッスンできます。
"$ viewContentLoaded-ビューが読み込まれると、DOMがレンダリングされた後に発生します。ビューの '$ scope'がイベントを発行します。" - リンク
$scope.$on('$viewContentLoaded',
function(event){ ... });
私はJQueryで角度のDOM操作を観察し、アプリの仕上げ(アプリの抽象に必要な事前定義された満足のいく状況の一種)を設定しました。たとえば、ng-repeaterが7つの結果を生成し、そこにこの目的のために、setIntervalを使用して監視関数を設定します。
$(document).ready(function(){
var interval = setInterval(function(){
if($("article").size() == 7){
myFunction();
clearInterval(interval);
}
},50);
});
ngRouteモジュールを使用しない場合、つまり$ viewContentLoadedイベントがない場合。
別のディレクティブメソッドを使用できます。
angular.module('someModule')
.directive('someDirective', someDirective);
someDirective.$inject = ['$rootScope', '$timeout']; //Inject services
function someDirective($rootScope, $timeout){
return {
restrict: "A",
priority: Number.MIN_SAFE_INTEGER, //Lowest priority
link : function(scope, element, attr){
$timeout(
function(){
$rootScope.$emit("Some:event");
}
);
}
};
}
したがって、trusktrの回答によると、優先順位は最も低くなります。さらに$ timeoutは、Angularがコールバックの実行前にイベントループ全体を実行するようにします。
$ rootScopeを使用。これは、ディレクティブをアプリケーションの任意のスコープに配置し、必要なリスナーのみに通知できるためです。
$ rootScope。$ emitは、すべての$ rootScope。$ onリスナーに対してのみイベントを発生させます。興味深い部分は、$ rootScope。$ broadcastがすべての$ rootScope。$ onおよび$ scope。$ onリスナーソースに通知することです。
AngularチームとこのGithubの問題によると:
$ viewContentLoadedイベントと$ includeContentLoadedイベントがあり、それぞれng-viewとng-includeで発行されます。これは、コンパイルがいつ完了したかを知ることができる限り近いと思います。
これに基づいて、これは現在信頼できる方法で行うことは不可能であると思われます。そうでなければ、Angularはそのままイベントを提供していたでしょう。
アプリのブートストラップは、ルートスコープでダイジェストサイクルを実行することを意味し、ダイジェストサイクル終了イベントもありません。
Angular 2 設計ドキュメントによると:
複数のダイジェストがあるため、モデルが安定していることを判断してコンポーネントに通知することはできません。これは、通知によってデータがさらに変更され、バインディングプロセスが再開される可能性があるためです。
これによると、これが不可能であるという事実は、Angular 2で書き換えを行うことを決定した理由の1つです。
サーバー側データ(JSP、PHP)でJSを生成する場合は、ロジックをサービスに追加できます。これは、コントローラーがロードされるときに自動的にロードされます。
さらに、すべてのディレクティブのコンパイル/リンクが完了したときに反応したい場合は、上記の適切なソリューションを初期化ロジックに追加できます。
module.factory('YourControllerInitService', function() {
// add your initialization logic here
// return empty service, because it will not be used
return {};
});
module.controller('YourController', function (YourControllerInitService) {
});
これらはすべて素晴らしいソリューションですが、現在ルーティングを使用している場合は、このソリューションが最も簡単で必要なコードの量が最も少ないことがわかりました。'resolve'プロパティを使用して、ルートがトリガーされる前にpromiseの完了を待機します。例えば
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}
}
})
この例で私はあなたを助けることができるかもしれません
カスタムのファンシーボックスに、補間された値でコンテンツを表示します。
サービスでは、「オープン」fancyboxメソッドで、私はやります
open: function(html, $compile) {
var el = angular.element(html);
var compiledEl = $compile(el);
$.fancybox.open(el);
}
$ compileはコンパイルされたデータを返します。コンパイルしたデータを確認できます