Ответы:
Просто догадка: почему бы не посмотреть, как это делает директива 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
По умолчанию прямая функция «ссылки» является пост-ссылкой, поэтому функция ссылки вашей внешней директивы 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
может быть хорошо, но не для конкретных элементов, и в этом случае функции ссылок лучше.
РЕДАКТИРОВАТЬ, пять месяцев спустя, 17 октября в 8:11 по тихоокеанскому времени:
Это не работает с частичными файлами, которые загружаются асинхронно. Вам нужно будет добавить бухгалтерию в свои частичные файлы (например, один из способов - заставить каждую частичную часть отслеживать, когда ее содержимое загружается, а затем генерировать событие, чтобы родительская область могла подсчитать, сколько частей было загружено, и, наконец, сделать то, что ей нужно для делать после загрузки всех партиалов).
ИЗМЕНИТЬ, 23 октября в 22: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);
}
});
}
};
});
РЕДАКТИРОВАТЬ, 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}}
замены.
Это работает только при первой загрузке элемента, но не при будущих изменениях. Это может не работать должным образом, если a $digest
продолжает происходить после первоначальной замены заполнителей (дайджест $ может произойти до 10 раз, пока данные не перестанут изменяться). Он подойдет для подавляющего большинства случаев использования.
РЕДАКТИРОВАТЬ, 31 октября в 19: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
тег на a div
of 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, который мне нужен, есть
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)
Он работает, предполагая, что приложение инициализируется, когда больше нет циклов $ digest. $ watch вызывается каждый цикл дайджеста, и поэтому запускается таймер (setTimeout, а не $ timeout, поэтому новый цикл дайджеста не запускается). Если цикл дайджеста не происходит в течение тайм-аута, предполагается, что приложение инициализировано.
Очевидно, что это не так точно, как решение satchmoruns (возможно, цикл дайджеста занимает больше времени, чем тайм-аут), но мое решение не требует, чтобы вы отслеживали модули, что значительно упрощает управление (особенно для более крупных проектов ). В любом случае, кажется, достаточно точным для моих требований. Надеюсь, поможет.
Если вы используете Angular UI Router , вы можете прослушивать $viewContentLoaded
событие.
«$ viewContentLoaded - запускается после загрузки представления, после рендеринга DOM . '$ scope' представления генерирует событие». - Ссылка
$scope.$on('$viewContentLoaded',
function(event){ ... });
Я наблюдаю за DOM-манипуляциями angular с помощью JQuery, и я установил финиш для своего приложения (своего рода предопределенная и удовлетворительная ситуация, которая мне нужна для моего приложения-аннотации), например, я ожидаю, что мой ng-Repeater выдаст 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. $ On, а также $ scope. $ На слушателях Источник
Согласно команде Angular и этой проблеме Github :
теперь у нас есть события $ viewContentLoaded и $ includeContentLoaded, которые генерируются в ng-view и ng-include соответственно. Я думаю, что это настолько близко, насколько можно понять, когда мы закончим компиляцию.
Исходя из этого, кажется, что в настоящее время это невозможно сделать надежным способом, иначе Angular предоставил бы событие из коробки.
Загрузка приложения подразумевает выполнение цикла дайджеста в корневой области, а также отсутствует событие завершения цикла дайджеста.
Согласно проектной документации Angular 2 :
Из-за множества дайджестов невозможно определить и уведомить компонент о том, что модель стабильна. Это связано с тем, что уведомление может дополнительно изменить данные, что может перезапустить процесс привязки.
В соответствии с этим тот факт, что это невозможно, является одной из причин, по которой было принято решение о переписывании в Angular 2.
У меня был фрагмент, который загружался после / основной партией, пришедшей через маршрутизацию.
Мне нужно было запустить функцию после того, как эта неполная часть загружена, и я не хотел писать новую директиву и понял, что можно использовать дерзкий ngIf
Контроллер родительского партиала:
$scope.subIsLoaded = function() { /*do stuff*/; return true; };
HTML неполного
<element ng-if="subIsLoaded()"><!-- more html --></element>
Если вы хотите сгенерировать JS с данными на стороне сервера (JSP, PHP), вы можете добавить свою логику в службу, которая будет загружаться автоматически при загрузке вашего контроллера.
Кроме того, если вы хотите отреагировать, когда все директивы завершены компиляцией / компоновкой, вы можете добавить соответствующие предлагаемые решения выше в логику инициализации.
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 я показываю содержимое с интерполированными значениями.
в сервисе, в "открытом" методе fancybox, я делаю
open: function(html, $compile) {
var el = angular.element(html);
var compiledEl = $compile(el);
$.fancybox.open(el);
}
$ compile возвращает скомпилированные данные. вы можете проверить скомпилированные данные