すべてのディレクティブがコンパイル/リンクを完了したときに、ページの読み込み/ブートストラップの終了を検出するための最良の方法は何であるか疑問に思いました。
すでにそこにあるイベントはありますか?ブートストラップ関数をオーバーロードする必要がありますか?
すべてのディレクティブがコンパイル/リンクを完了したときに、ページの読み込み/ブートストラップの終了を検出するための最良の方法は何であるか疑問に思いました。
すでにそこにあるイベントはありますか?ブートストラップ関数をオーバーロードする必要がありますか?
ちょっとした予感:ngCloakディレクティブがどのように機能するかを見てみませんか?明らかに、ngCloakディレクティブは、ロード後にコンテンツを表示することができます。私はngCloakを見ることは正確な答えにつながるに違いない...
1時間後に編集:わかり ました、まあ、私はngCloakを見ました、そしてそれは本当に短いです。これが明らかに意味することは、{{template}}式が評価されるまで(つまり、ロードされたテンプレート)、コンパイル関数が実行されないことです。したがって、ngCloakディレクティブの優れた機能です。
私の知識に基づいた推測は、ngCloakと同じ単純さでディレクティブを作成し、コンパイル関数でやりたいことを何でもすることです。:)アプリのルート要素にディレクティブを配置します。myOnloadのようなディレクティブを呼び出して、属性my-onloadとして使用できます。テンプレートがコンパイルされると(式が評価され、サブテンプレートがロードされると)、コンパイル関数が実行されます。
編集、23時間後: わかりました、それで私はいくつかの調査をしました、そして私はまた私自身の質問をしました。私が尋ねた質問は間接的にこの質問に関連していましたが、偶然にもこの質問を解決する答えに私を導きました。
答えは、単純なディレクティブを作成し、コードをディレクティブのリンク関数に配置できることです。これは、要素の準備ができたとき/ロードされたときに実行されます(ほとんどのユースケースでは、以下で説明します)。コンパイルおよびリンク関数が実行される順序に関するJoshの説明に基づいて、
このマークアップがある場合:
<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などの特定のディレクティブを使用する場合は注意してください。それらは独自のスコープを作成し、ディレクティブは起動しない場合があります。たとえば、新しいngElementReadyディレクティブをngIfも含む要素に配置し、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:
これは、非同期でロードされるパーシャルでは機能しません。パーシャルに簿記を追加する必要があります(たとえば、1つの方法は、各パーシャルがコンテンツのロードが完了したことを追跡し、イベントを発行して、親スコープがロードされたパーシャルの数をカウントし、最終的に必要なことを実行できるようにすることです。すべてのパーシャルがロードされた後に実行します)。
編集、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日午前0時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
プレースホルダーが最初に置き換えられた後も継続して発生する場合は、期待どおりに機能しない可能性があります($ダイジェストはデータの変更が停止するまで最大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: https://stackoverflow.com/questions/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!
Angularは、ページの読み込みが終了したことを通知する方法を提供していません。おそらく「終了」はアプリケーションによって異なるためです。たとえば、パーシャルの階層ツリーがある場合、1つが他をロードします。「完了」は、それらすべてがロードされたことを意味します。どのフレームワークでも、コードを分析して、すべてが完了したこと、またはまだ待機していることを理解するのに苦労します。そのためには、それをチェックして決定するためのアプリケーション固有のロジックを提供する必要があります。
角度の初期化がいつ完了するかを評価するのに比較的正確な解決策を考え出しました。
ディレクティブは次のとおりです。
.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でAngularのDOM操作を観察し、アプリの仕上げを設定しました(app-abstractに必要な、ある種の事前定義された満足のいく状況)。たとえば、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の問題によると:
これで、それぞれng-viewとng-includeで発行される$viewContentLoadedイベントと$includeContentLoadedイベントができました。これは、コンパイルが完了したときに知ることができる限り近いと思います。
これに基づいて、これを信頼できる方法で行うことは現在不可能であるように思われます。そうでなければ、Angularは箱から出してイベントを提供していたでしょう。
アプリのブートストラップは、ルートスコープでダイジェストサイクルを実行することを意味し、ダイジェストサイクル終了イベントもありません。
Angular 2の設計ドキュメントによると:
複数のダイジェストがあるため、モデルが安定していることをコンポーネントに判別して通知することはできません。これは、通知によってデータがさらに変更され、バインディングプロセスが再開される可能性があるためです。
これによると、これが不可能であるという事実が、Angular2での書き換えを決定した理由の1つです。
ルーティングを介して入ってくるメインパーシャルの後に/によってロードされるフラグメントがありました。
そのサブ部分がロードされた後に関数を実行する必要があり、新しいディレクティブを書きたくなかったので、生意気なものを使用できることがわかりましたngIf
親パーシャルのコントローラー:
$scope.subIsLoaded = function() { /*do stuff*/; return true; };
サブパーシャルのHTML
<element ng-if="subIsLoaded()"><!-- more html --></element>
サーバー側のデータ(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'プロパティを使用して、プロミスが完了するのを待ってからルートをトリガーします。例えば
$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はコンパイルされたデータを返します。コンパイルしたデータを確認できます