当angular.js完成加载时发送事件

想知道当所有指令完成编译/链接时,检测页面加载/引导结束的最佳方式是什么。

任何事件已经在那里? 我应该超载引导function?

只是一个预感:为什么不看ngCloak指令是怎么做的呢? 很明显,ngCloak指令设法在事情加载后显示内容。 我敢打赌,看ngCloak会导致确切的答案…

编辑1小时后:好吧,我看着ngCloak ,它真的很短。 这显然意味着编译函数在{{template}}expression式被计算之前(即它加载的模板)不会被执行,因此ngCloak指令的function很好。

我的教育猜测就是用ngCloak的简单指令做一个指令,然后在你的编译函数中做任何你想做的事情。 :)将指令放置在应用程序的根元素上。 你可以调用类似myOnload的指令,并将它用作属性my-onload。 编译函数将在模板编译完成后执行(expression式计算和子模板加载)。

编辑,23小时后:好吧,我做了一些研究,我也问了我自己的问题 。 我问的问题是间接与这个问题有关的,但是巧合的是我把答案解决了这个问题。

答案是你可以创build一个简单的指令,并把你的代码放在指令的链接函数中,当你的元素准备好/加载时,它会运行(大多数用例,下面将会解释)。 根据Josh对编译和链接函数执行顺序的描述 ,

如果你有这个标记:

<div directive1> <div directive2> <!-- ... --> </div> </div> 

然后AngularJS将通过按照一定顺序运行指令函数来创build指令:

 directive1: compile directive2: compile directive1: controller directive1: pre-link directive2: controller directive2: pre-link directive2: post-link directive1: post-link 

默认情况下,一个直接的“链接”函数是一个后链接,所以你的外部指令1的链接函数将在内部指令2的链接函数运行之后才会运行。 这就是为什么我们说在后链接中进行DOM操作是安全的。 因此,对于原始问题,应该没有问题从外部指令的链接函数访问子指令的内部html,但是dynamic插入的内容必须被编译,如上所述。

从这里我们可以得出结论:我们可以简单地做一个指令来执行我们的代码,当一切准备就绪/编译/链接/加载:

  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);来执行一个expression式(即一个函数) $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指令将被编译,但是它的链接函数将不会被执行。 注意:指令总是被编译的,但是它们的链接函数并不总是被执行,这取决于如上的某些情况。

编辑,几分钟后:

呵呵,为了完全回答这个问题,你现在可以从ng-element-ready属性中执行的expression式或者函数中$emit或者发送你的事件。 :)例如:

 <div data-ng-element-ready="$emit('someEvent')"> ... <div> 

编辑,甚至更多的几分钟后:

@ satchmorun的答案也适用,但只适用于初始加载。 这里有一个非常有用的SO问题 ,描述了执行的顺序,包括链接函数, app.run和其他。 所以,根据你的使用情况, app.run可能是好的,但不是特定的元素,在这种情况下,链接函数更好。

编辑,五个月后,10月17日8:11太平洋标准时间:

这不适用于asynchronous加载的部分。 您需要在分部中添加簿记(例如,一种方法是使每个部分跟踪其内容完成加载的情况,然后发出一个事件,以便父范围可以计算已加载的分部数,最后做到需要的部分做所有部分加载后)。

编辑,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分PST:

我改进了我的原始ngElementReady指令,并将其重命名为whenReady

 /* * The whenReady directive allows you to execute the content of a when-ready * attribute after the element is ready (ie 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 (ie {{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); } } } }]); 

例如,像这样使用它来加载一个元素并且{{placeholders}}还没有被replace时触发一些someFunction

 <div when-ready="someFunction()"> <span ng-repeat="item in items">{{item.property}}</span> </div> 

在所有item.property占位符被replace之前, someFunction将被调用。

根据需要评估尽可能多的expression式,并将最后一个expression式设置为true以等待{{placeholders}}进行如下评估:

 <div when-ready="someFunction(); anotherFunction(); true"> <span ng-repeat="item in items">{{item.property}}</span> </div> 

{{placeholders}}被replace之后, someFunctionanotherFunction将被触发。

这只在第一次加载元素时起作用,而不是将来的变化。 如果在占位符最初被replace之后, $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 (ie 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 (ie * {{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 }); 

编辑:手动初始化到救援

所以有人指出,当DOM准备好并链接起来时, run不会被调用。 当ng-app引用的模块的$injector已经加载了所有的依赖关系(这与DOM编译步骤分开)时,它会被调用。

我再看看手动初始化 ,看来这应该做的伎俩。

我已经做了一个小提琴来说明 。

HTML很简单:

 <html> <body> <test-directive>This is a test</test-directive> </body> </html> 

请注意,缺less一个ng-app 。 我有一个指令可以做一些DOM操作,所以我们可以确定事物的顺序和时间。

像往常一样,创build一个模块:

 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"); } }; } }; }); 

我们将用class test-directivedivreplacetest-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; } 

在做任何事情之前,在DOM中不应该有一类带有一个test-directive元素,在完成之后应该有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没有提供一个方式来表明一个页面完成加载,也许因为“完成”取决于您的应用程序 。 例如,如果您有部分树的分层树,则加载其他树。 “完成”将意味着所有这些都已经被加载。 任何框架都很难分析你的代码,并理解一切都已经完成,或者仍然等待。为此,你将不得不提供特定于应用程序的逻辑来检查和确定。

谢谢

利奥尔

我提出了一个解决scheme,在angular度初始化完成的时候评估是比较准确的。

该指令是:

 .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)进行监听$scope.$on('initialised', fn)

它的工作原理是当没有更多$摘要循环时,应用程序被初始化。 $ watch被称为每个摘要循环,所以一个定时器被启动(setTimeout不是$超时,所以不会触发一个新的摘要循环)。 如果摘要周期在超时内没有发生,那么应用程序被假定为已经初始化。

显然,它不像satchmoruns解决scheme那样精确(因为摘要循环可能比超时更长),但是我的解决scheme不需要跟踪模块,这使得pipe理更容易(特别是对于大型项目)。 无论如何,似乎是足够准确的我的要求。 希望它有帮助。

如果您正在使用Angular UI路由器 ,则可以侦听$viewContentLoaded事件。

“$ viewContentLoaded – 在DOM被渲染后加载视图时触发,视图的'$ scope'发出事件。 – 链接

 $scope.$on('$viewContentLoaded', function(event){ ... }); 

我观察DOM的angular度与JQuery操作,我确定了我的应用程序完成(某种预定义和令人满意的情况下,我需要我的应用程序抽象)例如,我期望我的中继器产生7结果,并为我将在setInterval的帮助下为此设置一个观察函数。

 $(document).ready(function(){ var interval = setInterval(function(){ if($("article").size() == 7){ myFunction(); clearInterval(interval); } },50); }); 

根据Angular团队和这个Github的问题 :

我们现在分别在ng-view和ng-include中发布$ viewContentLoaded和$ includeContentLoaded事件。 我认为,当我们完成编辑工作的时候,我们已经可以了解到这一点。

基于此,目前看来目前还不可能以可靠的方式进行,否则Angular会提供开箱即用的活动。

引导应用意味着在根作用域上运行摘要循环,并且也没有摘要循环完成事件。

根据Angular 2的devise文档 :

由于多个摘要,不可能确定并通知组件该模型是稳定的。 这是因为通知可以进一步更改数据,这可以重新启动绑定过程。

据此,这是不可能的事实是决定采取Angular2重写的原因之一。

如果你不使用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的回答,它的优先级最低。 Plus $ timeout将导致Angular在执行callback之前运行整个事件循环。

使用$ rootScope ,因为它允许将指令放在应用程序的任何范围内,并只通知必要的监听器。

$ rootScope。$ emit只会触发所有$ rootScope。$上的事件。 有趣的部分是$ rootScope。$ broadcast将通知所有的$ rootScope。$ on以及$ scope。$ on listeners 来源

我有一个片段被加载 – 在通过路由传入的主要部分之后。

我需要运行一个函数后,加载子部分,我不想写一个新的指令,并找出你可以使用一个厚脸皮ngIf

父母部分的控制者:

 $scope.subIsLoaded = function() { /*do stuff*/; return true; }; 

子部分的HTML

 <element ng-if="subIsLoaded()"><!-- more html --></element> 

如果你想用服务器端数据(JSP,PHP)生成JS,你可以将你的逻辑添加到一个服务中,当你的控制器被加载时,这个服务会被自动加载。

另外,如果你想在所有指令完成编译/链接时做出反应,你可以在初始化逻辑中添加上面提出的合适解决scheme。

 module.factory('YourControllerInitService', function() { // add your initialization logic here // return empty service, because it will not be used return {}; }); module.controller('YourController', function (YourControllerInitService) { }); 

这些都是很好的解决scheme,但是,如果你正在使用路由,那么我发现这个解决scheme是最简单和最less量的代码。 使用'resolve'属性在触发路由之前等待承诺完成。 例如

 $routeProvider .when("/news", { templateUrl: "newsView.html", controller: "newsController", resolve: { message: function(messageService){ return messageService.getMessage(); } } 

})

点击这里查看完整文档 – 感谢K. Scott Allen

可能是我可以帮助你通过这个例子

在自定义fancybox我显示带有插值的内容。

在服务中,在“开放”的fancybox方法中,我这样做

 open: function(html, $compile) { var el = angular.element(html); var compiledEl = $compile(el); $.fancybox.open(el); } 

$编译返回编译的数据。 你可以检查编译的数据