Answers:
只是预感:为什么不看看ngCloak指令是如何执行的呢?显然,ngCloak伪指令设法在内容加载后显示内容。我敢打赌ngCloak会导致确切的答案...
1小时后编辑: 好的,我看了ngCloak,它真的很短。显然,这意味着编译函数只有在对{{template}}表达式求值(即,它加载的模板)之后才能执行,因此ngCloak指令具有很好的功能。
我的有根据的猜测是只用ngCloak一样简单的方式编写一条指令,然后在您的编译函数中执行您想做的任何事情。:)将指令放置在应用程序的根元素上。您可以像myOnload这样调用该指令,并将其用作my-onload属性。模板编译完成(评估表达式并加载子模板)后,将执行compile函数。
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
默认情况下,直接的“链接”功能是后链接,因此,外部指令1的链接功能将在内部指令2的链接功能运行后才能运行。这就是为什么我们说在后链接中进行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指令。或者,例如,如果您将新的ngElementReady指令放在也具有ngInclude指令的元素上,则如果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
可能会很好,但对于特定元素而言则不然,在这种情况下,链接功能会更好。
编辑,五个月后,太平洋标准时间10月17日晚上8:11:
这不适用于异步加载的局部。您需要在部分文件中添加簿记(例如,一种方法是让每个部分跟踪其内容何时完成加载,然后发出一个事件,以便父范围可以计算已加载了多少部分文件并最终执行所需的操作加载完所有的部分后执行)。
编辑,太平洋标准时间10月23日晚上10:52:
我做了一个简单的指令,用于在加载图像时触发一些代码:
/*
* 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:
我改进了原始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:
好吧,这可能是我的最后也是最后一次更新。这可能适用于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
class 的标签替换标记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 Router,则可以侦听$viewContentLoaded
事件。
“ $ viewContentLoaded- 呈现DOM后,加载视图后触发。视图的'$ scope'发出事件。- 链接
$scope.$on('$viewContentLoaded',
function(event){ ... });
我用JQuery观察了对Ang的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。$仅在侦听器上触发一个事件。有趣的是,$ rootScope。$广播会通知所有$ rootScope。$上以及$范围。$上监听 源
根据Angular团队和这个Github问题:
我们现在有分别在ng-view和ng-include中发出的$ viewContentLoaded和$ includeContentLoaded事件。我认为这与我们完成编译的时间差不多。
基于此,目前似乎不可能以可靠的方式进行此操作,否则Angular会立即提供该事件。
引导应用程序意味着在根作用域上运行摘要循环,并且也没有摘要循环完成事件。
根据Angular 2 设计文档:
由于有多个摘要,因此无法确定并通知组件该模型是稳定的。这是因为通知可以进一步更改数据,从而可以重新启动绑定过程。
据此,不可能做到这一点的事实是做出决定重写Angular 2的原因之一。
如果要使用服务器端数据(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返回编译后的数据。你可以检查编译后的数据