Respostas:
Apenas um palpite: por que não ver como a diretiva ngCloak faz isso? Claramente, a diretiva ngCloak consegue mostrar o conteúdo depois que as coisas são carregadas. Aposto que olhar para o ngCloak levará à resposta exata ...
EDITAR 1 hora depois: Ok, bem, olhei para o ngCloak e ele é muito curto. O que isso obviamente implica é que a função de compilação não será executada até que as expressões {{template}} tenham sido avaliadas (isto é, o template que ele carregou), portanto, a boa funcionalidade da diretiva ngCloak.
Meu palpite seria apenas fazer uma diretiva com a mesma simplicidade do ngCloak e, em seguida, em sua função de compilação, fazer o que quiser. :) Coloque a diretiva no elemento raiz do seu aplicativo. Você pode chamar a diretiva de algo como myOnload e usá-la como um atributo my-onload. A função de compilação será executada assim que o modelo for compilado (expressões avaliadas e sub-modelos carregados).
EDITAR, 23 horas depois: Ok, então fiz algumas pesquisas e também fiz minha própria pergunta . A pergunta que fiz estava indiretamente relacionada a esta pergunta, mas coincidentemente me levou à resposta que resolve essa pergunta.
A resposta é que você pode criar uma diretiva simples e colocar seu código na função de link da diretiva, que (para a maioria dos casos de uso, explicados abaixo) será executada quando seu elemento estiver pronto / carregado. Com base na descrição de Josh da ordem em que as funções de compilação e link são executadas ,
se você tiver esta marcação:
<div directive1> <div directive2> <!-- ... --> </div> </div>
Em seguida, o AngularJS criará as diretivas executando funções de diretiva em uma determinada ordem:
directive1: compile directive2: compile directive1: controller directive1: pre-link directive2: controller directive2: pre-link directive2: post-link directive1: post-link
Por padrão, uma função de "link" direta é um post-link, então a função de link da sua diretiva externa1 não será executada até que a função de link da diretiva interna2 seja executada. É por isso que dizemos que só é seguro fazer manipulação de DOM no post-link. Portanto, em relação à pergunta original, não deve haver nenhum problema ao acessar o html interno da diretiva filho a partir da função de link da diretiva externa, embora o conteúdo inserido dinamicamente deva ser compilado, como dito acima.
Disto podemos concluir que podemos simplesmente fazer uma diretiva para executar nosso código quando tudo estiver pronto / compilado / vinculado / carregado:
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.
}
};
}]);
Agora o que você pode fazer é colocar a diretiva ngElementReady no elemento raiz do aplicativo e o console.log
será acionado quando for carregado:
<body data-ng-app="MyApp" data-ng-element-ready="">
...
...
</body>
É simples assim! Basta fazer uma diretiva simples e usá-la. ;)
Você pode personalizá-lo ainda mais para que possa executar uma expressão (ou seja, uma função) adicionando $scope.$eval($attributes.ngElementReady);
a ele:
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.
}
};
}]);
Então você pode usá-lo em qualquer elemento:
<body data-ng-app="MyApp" data-ng-controller="BodyCtrl" data-ng-element-ready="bodyIsReady()">
...
<div data-ng-element-ready="divIsReady()">...<div>
</body>
Apenas certifique-se de ter suas funções (por exemplo, bodyIsReady e divIsReady) definidas no escopo (no controlador) sob o qual seu elemento vive.
Advertências: Eu disse que isso funcionará na maioria dos casos. Tenha cuidado ao usar certas diretivas como ngRepeat e ngIf. Eles criam seu próprio escopo e sua diretiva não pode disparar. Por exemplo, se você colocar nossa nova diretiva ngElementReady em um elemento que também tem ngIf, e a condição do ngIf for avaliada como falsa, então nossa diretiva ngElementReady não será carregada. Ou, por exemplo, se você colocar nossa nova diretiva ngElementReady em um elemento que também tem uma diretiva ngInclude, nossa diretiva não será carregada se o modelo para o ngInclude não existir. Você pode contornar alguns desses problemas certificando-se de aninhar as diretivas em vez de colocá-las todas no mesmo elemento. Por exemplo, fazendo isso:
<div data-ng-element-ready="divIsReady()">
<div data-ng-include="non-existent-template.html"></div>
<div>
em vez disso:
<div data-ng-element-ready="divIsReady()" data-ng-include="non-existent-template.html"></div>
A diretiva ngElementReady será compilada no último exemplo, mas sua função de link não será executada. Nota: as diretivas são sempre compiladas, mas suas funções de link nem sempre são executadas, dependendo de certos cenários como o acima.
EDITAR, alguns minutos depois:
Ah, e para responder totalmente à pergunta, você pode agora $emit
ou $broadcast
seu evento a partir da expressão ou função que é executada no ng-element-ready
atributo. :) Por exemplo:
<div data-ng-element-ready="$emit('someEvent')">
...
<div>
EDITAR, ainda mais alguns minutos depois:
A resposta de @satchmorun também funciona, mas apenas para o carregamento inicial. Aqui está uma pergunta SO muito útil que descreve a ordem em que as coisas são executadas, incluindo funções de link app.run
, e outros. Portanto, dependendo do seu caso de uso, app.run
pode ser bom, mas não para elementos específicos; nesse caso, as funções de link são melhores.
EDITAR, cinco meses depois, 17 de outubro às 8:11 PST:
Isso não funciona com parciais que são carregados de forma assíncrona. Você precisará adicionar contabilidade em seus parciais (por exemplo, uma maneira é fazer com que cada parcial mantenha o controle de quando seu conteúdo é carregado e, em seguida, emita um evento para que o escopo pai possa contar quantas parciais foram carregadas e finalmente fazer o que precisa fazer depois que todas as parciais forem carregadas).
EDITAR, 23 de outubro às 22h52 PST:
Fiz uma diretiva simples para disparar algum código quando uma imagem é carregada:
/*
* 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);
}
});
}
};
});
EDITAR, 24 de outubro às 12h48 PST:
Eu melhorei minha ngElementReady
diretiva original e a renomei para 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);
}
}
}
}]);
Por exemplo, use-o assim para disparar someFunction
quando um elemento for carregado e {{placeholders}}
ainda não substituído:
<div when-ready="someFunction()">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
será chamado antes que todos os item.property
marcadores sejam substituídos.
Avalie quantas expressões quiser e faça a última expressão true
a aguardar para {{placeholders}}
ser avaliada assim:
<div when-ready="someFunction(); anotherFunction(); true">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
e anotherFunction
será demitido após {{placeholders}}
ter sido substituído.
Isso só funciona na primeira vez que um elemento é carregado, não em alterações futuras. Pode não funcionar como desejado se um $digest
continuar ocorrendo após os marcadores terem sido substituídos inicialmente (um $ digest pode acontecer até 10 vezes até que os dados parem de mudar). Será adequado para a grande maioria dos casos de uso.
EDIT, 31 de outubro às 19h26 PST:
Tudo bem, esta é provavelmente minha última e última atualização. Isso provavelmente funcionará para 99,999 dos casos de uso por aí:
/*
* 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);
}
}
};
}]);
Use assim
<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>
Claro, provavelmente pode ser otimizado, mas vou deixar por isso mesmo. requestAnimationFrame é bom.
Na documentação deangular.Module
, há uma entrada que descreve a run
função:
Use este método para registrar o trabalho que deve ser executado quando o injetor terminar de carregar todos os módulos.
Portanto, se você tiver algum módulo que seja seu aplicativo:
var app = angular.module('app', [/* module dependencies */]);
Você pode executar coisas depois que os módulos forem carregados com:
app.run(function() {
// Do post-load initialization stuff here
});
Portanto, foi apontado que o run
não é chamado quando o DOM está pronto e vinculado. Ele é chamado quando $injector
for o módulo referenciado por ng-app
carrega todas as suas dependências, que são separadas da etapa de compilação do DOM.
Dei outra olhada na inicialização manual e parece que isso deve funcionar.
Fiz um violino para ilustrar .
O HTML é simples:
<html>
<body>
<test-directive>This is a test</test-directive>
</body>
</html>
Observe a falta de um ng-app
. E eu tenho uma diretiva que fará alguma manipulação do DOM, para que possamos ter certeza da ordem e do tempo das coisas.
Como de costume, um módulo é criado:
var app = angular.module('app', []);
E aqui está a diretiva:
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"); }
};
}
};
});
Vamos substituir a test-directive
tag por um div
of class test-directive
e envolver seu conteúdo em um h1
.
Eu adicionei uma função de compilação que retorna funções de pré e pós-link para que possamos ver quando essas coisas são executadas.
Aqui está o resto do código:
// 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;
}
Antes de fazermos qualquer coisa, não deve haver elementos com uma classe de test-directive
no DOM e, depois de terminarmos, deve haver 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'));
});
É muito simples. Quando o documento estiver pronto, chame angular.bootstrap
com o elemento raiz do seu aplicativo e uma matriz de nomes de módulos.
Na verdade, se você anexar uma run
função ao app
módulo , verá que ela é executada antes de qualquer compilação.
Se você executar o violino e assistir o console, verá o seguinte:
before (should be 0): 0
Compiling test-directive
Prelink
Postlink
after (should be 1): 1 <--- success!
run
, é
$timeout( initMyPlugins,0)
funciona dentro da minha diretiva, todo o html que preciso está lá
O Angular não forneceu uma maneira de sinalizar quando uma página terminou de carregar, talvez porque "concluído" dependa de seu aplicativo . Por exemplo, se você tem uma árvore hierárquica de parciais, uma carregando as outras. "Concluir" significaria que todos eles foram carregados. Qualquer estrutura teria dificuldade em analisar seu código e entender que tudo está feito ou ainda esperado. Para isso, você teria que fornecer uma lógica específica do aplicativo para verificar e determinar isso.
Eu descobri uma solução que é relativamente precisa para avaliar quando a inicialização angular está completa.
A diretiva é:
.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);
});
}
};
}]);
Isso pode então ser apenas adicionado como um atributo ao body
elemento e, em seguida, ouvido para usar$scope.$on('initialised', fn)
Ele funciona assumindo que o aplicativo é inicializado quando não há mais ciclos de $ digest. $ watch é chamado a cada ciclo de resumo e, portanto, um cronômetro é iniciado (setTimeout e não $ timeout, portanto, um novo ciclo de resumo não é acionado). Se um ciclo de resumo não ocorrer dentro do tempo limite, presume-se que o aplicativo foi inicializado.
Obviamente, não é tão preciso quanto a solução satchmoruns (já que é possível que um ciclo de resumo demore mais do que o tempo limite), mas minha solução não precisa que você acompanhe os módulos, o que a torna muito mais fácil de gerenciar (especialmente para projetos maiores ) De qualquer forma, parece ser preciso o suficiente para minhas necessidades. Espero que ajude.
Se você estiver usando o Angular UI Router , poderá ouvir o $viewContentLoaded
evento.
"$ viewContentLoaded - disparado assim que a visão é carregada, depois que o DOM é renderizado . O '$ escopo' da visão emite o evento." - Link
$scope.$on('$viewContentLoaded',
function(event){ ... });
observei a manipulação DOM do angular com JQuery e defini um acabamento para meu aplicativo (algum tipo de situação predefinida e satisfatória que preciso para meu aplicativo-resumo) por exemplo, espero que meu repetidor ng produza 7 resultados e aí para i irá definir uma função de observação com a ajuda de setInterval para este propósito.
$(document).ready(function(){
var interval = setInterval(function(){
if($("article").size() == 7){
myFunction();
clearInterval(interval);
}
},50);
});
Se você não usa o módulo ngRoute , ou seja, você não tem o evento $ viewContentLoaded .
Você pode usar outro método de diretiva:
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");
}
);
}
};
}
De acordo com a resposta de trusktr, ele tem a prioridade mais baixa. Além disso, $ timeout fará com que o Angular execute um loop de evento inteiro antes da execução do callback.
$ rootScope usado, porque permite colocar diretivas em qualquer escopo da aplicação e notificar apenas os ouvintes necessários.
$ rootScope. $ emit disparará um evento para todos os $ rootScope. $ apenas nos ouvintes. A parte interessante é que $ rootScope. $ Broadcast notificará todos os $ rootScope. $ E também $ scope. $ Nos ouvintes. Fonte
De acordo com a equipe Angular e este problema no Github :
agora temos eventos $ viewContentLoaded e $ includeContentLoaded que são emitidos em ng-view e ng-include respectivamente. Acho que isso é o mais perto que se pode chegar de saber quando terminarmos a compilação.
Com base nisso, parece que atualmente não é possível fazer de uma forma confiável, caso contrário, o Angular teria fornecido o evento pronto para uso.
A inicialização do aplicativo implica a execução do ciclo de resumo no escopo raiz e também não há um evento de conclusão do ciclo de resumo.
De acordo com os documentos de design do Angular 2 :
Por causa de vários resumos, é impossível determinar e notificar o componente de que o modelo é estável. Isso ocorre porque a notificação pode alterar ainda mais os dados, o que pode reiniciar o processo de vinculação.
De acordo com isso, o fato de isso não ser possível é uma das razões pelas quais foi tomada a decisão de ir para uma reescrita em Angular 2.
Eu tinha um fragmento que estava sendo carregado após / pelo parcial principal que veio por meio de roteamento.
Eu precisava executar uma função após o carregamento dessa subparcialidade e não queria escrever uma nova diretiva e descobri que você poderia usar um ngIf
Controlador da parcial parental:
$scope.subIsLoaded = function() { /*do stuff*/; return true; };
HTML de subparcial
<element ng-if="subIsLoaded()"><!-- more html --></element>
Se você deseja gerar JS com dados do lado do servidor (JSP, PHP), você pode adicionar sua lógica a um serviço, que será carregado automaticamente quando seu controlador for carregado.
Além disso, se você quiser reagir quando todas as diretivas tiverem terminado de compilar / vincular, você pode adicionar as soluções propostas apropriadas acima na lógica de inicialização.
module.factory('YourControllerInitService', function() {
// add your initialization logic here
// return empty service, because it will not be used
return {};
});
module.controller('YourController', function (YourControllerInitService) {
});
Todas essas são ótimas soluções. No entanto, se você estiver usando o roteamento, achei essa solução a mais fácil e com a menor quantidade de código necessária. Usar a propriedade 'resolve' para aguardar a conclusão de uma promessa antes de acionar a rota. por exemplo
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}
}
})
Clique aqui para obter os documentos completos - Crédito para K. Scott Allen
pode ser que eu possa te ajudar com este exemplo
Na caixa de fantasia personalizada, mostro o conteúdo com valores interpolados.
no serviço, no método "open" fancybox, eu faço
open: function(html, $compile) {
var el = angular.element(html);
var compiledEl = $compile(el);
$.fancybox.open(el);
}
o $ compile retorna dados compilados. você pode verificar os dados compilados