Отправка события при завершении работы angular.js

Учтите, что лучше всего определить, как завершить загрузку / загрузку страницы, когда все директивы выполняли компиляцию / компоновку.

Любое событие уже есть? Должен ли я перегружать функцию начальной загрузки?

Просто догадка: почему бы не посмотреть, как это делает директива ngCloak? Очевидно, директива ngCloak позволяет показывать контент после загрузки. Бьюсь об заклад, ngCloak приведет к точному ответу …

EDIT 1 час спустя: Хорошо, хорошо, я посмотрел на ngCloak, и это действительно коротко. Очевидно, что это означает, что функция компиляции не будет выполняться до тех пор, пока выражения {{template}} не будут оценены (т. Е. Загруженный шаблон), таким образом, хорошая функциональность директивы ngCloak.

Моя образованная догадка заключалась в том, чтобы просто создать директиву с той же простотой ngCloak, а затем в вашей функции компиляции сделайте все, что вы хотите сделать. 🙂 Поместите директиву в корневой элемент вашего приложения. Вы можете вызвать директиву что-то вроде myOnload и использовать ее как атрибут my-onload. Функция компиляции будет выполняться после того, как шаблон был скомпилирован (обработаны выражения и подшаблоны).

EDIT, 23 часа спустя: Хорошо, поэтому я провел некоторое исследование, и я также задал свой вопрос . Вопрос, который я задал, косвенно связан с этим вопросом, но он случайно приводит меня к ответу, который решает этот вопрос.

Ответ заключается в том, что вы можете создать простую директиву и поместить свой код в функцию ссылки директивы, которая (для большинства случаев использования, описанная ниже) будет выполняться, когда ваш элемент будет готов / загружен. Основываясь на описании Джошем порядка, в котором выполняются функции компиляции и ссылки ,

если у вас есть эта разметка:

Тогда AngularJS создаст директивы, запустив директивные функции в определенном порядке:

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

По умолчанию прямая функция «ссылка» – это пост-ссылка, поэтому ваша функция ссылки на внешнюю директиву не будет работать до тех пор, пока не будет запущена функция ссылки на внутреннюю директиву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 будет срабатывать, когда он будет загружен:

  ... ...  

Это так просто! Просто сделайте простую директиву и используйте ее. 😉

Вы можете дополнительно настроить его, чтобы он мог выполнять выражение (т.е. функцию), добавляя $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. } }; }]); 

Затем вы можете использовать его на любом элементе:

  ... 
...

Просто убедитесь, что у вас есть свои функции (например, bodyIsReady и divIsReady), определенные в области (в controllerе), в которой находится ваш элемент.

Предостережения: Я сказал, что это будет работать в большинстве случаев. Будьте внимательны при использовании определенных директив, таких как ngRepeat и ngIf. Они создают свой собственный масштаб, и ваша директива не может срабатывать. Например, если вы поместите нашу новую директиву ngElementReady на элемент, который также имеет ngIf, а условие ngIf будет равно false, тогда наша директива ngElementReady не будет загружена. Или, например, если вы поместите нашу новую директиву ngElementReady на элемент, который также имеет директиву ngInclude, наша директива не будет загружена, если шаблон для ngInclude не существует. Вы можете обойти некоторые из этих проблем, убедившись, что вы вставляете директивы вместо того, чтобы помещать их все в один и тот же элемент. Например, делая следующее:

 

вместо этого:

 

Директива ngElementReady будет скомпилирована в последнем примере, но функция ссылки не будет выполнена. Примечание: директивы всегда скомпилированы, но их функции ссылок не всегда выполняются в зависимости от определенных сценариев, подобных приведенным выше.

EDIT, через несколько минут:

О, и чтобы полностью ответить на вопрос, вы можете теперь $emit или $broadcast ваше событие из выражения или функции, которая выполняется в атрибуте ng-element-ready . 🙂 Например:

 
...

EDIT, еще несколько минут спустя:

@ Ответ satchmorun тоже работает, но только для начальной загрузки. Вот очень полезный вопрос SO , описывающий выполнение заказа, включая функции ссылок, app.run и другие. Таким образом, в зависимости от вашего app.run использования app.run может быть хорошим, но не для определенных элементов, и в этом случае функции ссылок лучше.

EDIT, пять месяцев спустя, 17 октября в 8:11 PST:

Это не работает с частичными, которые загружаются асинхронно. Вам нужно будет добавить бухгалтерию в свои частичные (например, один из способов состоит в том, чтобы каждый частичный контроль, когда его содержимое загружается, затем выделяет событие, поэтому родительская область может подсчитывать, сколько частичных файлов загрузилось и, наконец, сделать то, что нужно после того, как все частичные загружены).

EDIT, 23 октября в 22: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); } }); } }; }); 

EDIT, 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); } } } }]); 

Например, используйте его так, чтобы уволить someFunction когда элемент загружен, и {{placeholders}} еще не заменены:

 
{{item.property}}

someFunction будет вызвана до того, item.property будут заменены все заменители item.property .

Вычислите столько выражений, сколько хотите, и сделайте последнее выражение true ожидая, что {{placeholders}} будет оцениваться следующим образом:

 
{{item.property}}

someFunction и anotherFunction будут запущены после {{placeholders}} .

Это работает только при первом загрузке элемента, а не в будущих изменениях. Он может работать не так, как нужно, если $digest продолжает происходить после того, как заполнители первоначально были заменены (дайджест $ может произойти до 10 раз, пока данные не перестанут меняться). Он будет подходящим для подавляющего большинства случаев использования.

EDIT, 31 октября в 19: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); } } }; }]); 

Используйте его так

 
isReady will fire when this {{placeholder}} has been evaluated and when checkIfReady finally returns true. checkIfReady might contain code like `$('.some-element').length`.

Конечно, его можно оптимизировать, но я просто оставлю это. requestAnimationFrame приятно.

В документах для angular.Module есть запись, описывающая функцию run :

Используйте этот метод для регистрации работы, которая должна выполняться при выполнении инжектора, загружая все модули.

Поэтому, если у вас есть модуль, который является вашим приложением:

 var app = angular.module('app', [/* module dependencies */]); 

Вы можете запускать материал после загрузки модhive:

 app.run(function() { // Do post-load initialization stuff here }); 

EDIT: ручная инициализация на помощь

Таким образом, было указано, что run не вызывается, когда DOM готов и связан. Он вызывается, когда $injector для модуля, на который ссылается ng-app , загружает все его зависимости, которые отделены от этапа компиляции DOM.

Я еще раз взглянул на ручную инициализацию , и, похоже, это должно сделать трюк.

Я сделал скрипку, чтобы проиллюстрировать .

HTML прост:

   This is a test   

Обратите внимание на отсутствие ng-app . И у меня есть директива, которая будет выполнять некоторые манипуляции с DOM, поэтому мы можем следить за порядком и сроками вещей.

Как обычно, создается модуль:

 var app = angular.module('app', []); 

И вот директива:

 app.directive('testDirective', function() { return { restrict: 'E', template: '

', 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; } 

Прежде чем мы что-либо сделали, в DOM не должно быть элементов с classом 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 с корневым элементом вашего приложения и массивом имен модhive.

Фактически, если вы присоедините функцию run к модулю app , вы увидите, что он запускается до того, как произойдет компиляция.

Если вы запустите скрипку и посмотрите консоль, вы увидите следующее:

 before (should be 0): 0 Compiling test-directive Prelink Postlink after (should be 1): 1 <--- success! 

Хотелось добавить еще одно осознание, которое у меня было о моем собственном вопросе: Angular не предоставил способ сообщить, когда страница закончила загрузку, возможно, потому что «закончено» зависит от вашего приложения . например, если у вас есть иерархическое дерево партитур, одна загрузка других. «finish» означает, что все они загружены. Любая инфраструктура будет нелегко анализировать ваш код и понимать, что все сделано или все еще ждет. Для этого вам придется предоставить логику приложения для проверки и определения этого.

благодаря

Лиор

Я придумал решение, которое относительно точно оценивает, когда завершается угловая инициализация.

Директива:

 .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); }); } }; }]); . .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)

Он работает, предполагая, что приложение инициализируется, когда циклов дайджеста больше нет. $ watch вызывается каждый цикл дайджеста и поэтому запускается таймер (setTimeout не $ timeout, поэтому новый цикл дайджеста не запускается). Если цикл тайм-аута не возникает в течение таймаута, предполагается, что приложение инициализировано.

Это, очевидно, не так точно, как решение satchmoruns (так как это возможно, цикл дайджест занимает больше времени, чем таймаут), но мое решение не требует от вас отслеживания модhive, что делает его намного проще в управлении (особенно для крупных проектов ). Во всяком случае, кажется, что я достаточно точен для своих требований. Надеюсь, поможет.

Если вы используете Angular UI Router , вы можете прослушивать событие $viewContentLoaded .

«$ viewContentLoaded – запускается после загрузки представления после того, как DOM отображается .« $ scope »представления испускает событие». – Ссылка

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

Я наблюдаю за манипуляцией DOM с угловым с JQuery, и я установил финиш для своего приложения (какая-то предопределенная и удовлетворительная ситуация, которая мне нужна для моего абстрактного приложения), например, я ожидаю, что мой ng-повторитель произведет 7 результат и там для i будет устанавливать функцию наблюдения с помощью 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. $ Broadcast будет уведомлять все $ rootScope. $, А также $ scope. $ На слушателях Source

Согласно команде Angular и этой проблеме Github :

теперь у нас есть события $ viewContentLoaded и $ includeContentLoaded, которые испускаются в ng-view и ng-include соответственно. Я думаю, что это так близко, как можно понять, когда мы закончили компиляцию.

Исходя из этого, похоже, что в настоящее время это невозможно сделать надежным способом, в противном случае Угловая предоставила бы событие из коробки.

При загрузке приложения подразумевается запуск цикла дайджеста в корневой области, а также не завершенное событие цикла дайджеста.

Согласно проектам дизайна Angular 2:

Из-за нескольких дайджестов невозможно определить и оповестить компонент о стабильности модели. Это связано с тем, что уведомление может дополнительно изменять данные, которые могут перезапустить процесс привязки.

В соответствии с этим тот факт, что это невозможно, является одной из причин, почему было принято решение переписать в Angular 2.

У меня был fragment, который загружался после / из основного части, который пришел через маршрутизацию.

Мне нужно было запустить функцию после того, как этот субпартент загрузился, и я не хотел писать новую директиву и понял, что вы можете использовать нахальный ngIf

Контроллер родительского частичного:

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

HTML субчастичного

  

Если вы хотите сгенерировать JS с данными на стороне сервера (JSP, PHP), вы можете добавить свою логику в сервис, который будет загружаться автоматически при загрузке вашего controllerа.

Кроме того, если вы хотите реагировать, когда все директивы выполняются компиляцией / связыванием, вы могли бы добавить соответствующие предлагаемые решения выше в логике инициализации.

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

Все это отличные решения. Однако, если вы используете Routing, я нашел это решение самым легким и наименьшим количеством необходимого кода. Используя свойство «разрешение», чтобы дождаться завершения обещания до запуска маршрута. например

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

})

Нажмите здесь для получения полного документа – Кредит К. Скотт Аллен

возможно, я могу помочь вам в этом примере

В пользовательском fancybox я показываю содержимое с интерполированными значениями.

в службе, в «открытом» методе fancybox, я делаю

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

$ compile возвращает скомпилированные данные. вы можете проверить скомпилированные данные

Давайте будем гением компьютера.