Queria saber qual é a melhor maneira de detectar o término do carregamento / bootstrapping da página, quando todas as diretivas terminam de compilar / vincular.
Algum evento já está aí? Devo sobrecarregar a função de bootstrap?
Queria saber qual é a melhor maneira de detectar o término do carregamento / bootstrapping da página, quando todas as diretivas terminam de compilar / vincular.
Algum evento já está aí? Devo sobrecarregar a função de bootstrap?
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 de
angular.Module
, há uma entrada que descreve arun
função:Portanto, se você tiver algum módulo que seja seu aplicativo:
Você pode executar coisas depois que os módulos forem carregados com:
EDIT: Inicialização manual para o resgate
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 porng-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:
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:
E aqui está a diretiva:
Vamos substituir a
test-directive
tag por umdiv
of classtest-directive
e envolver seu conteúdo em umh1
.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:
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.É 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 aoapp
módulo , verá que ela é executada antes de qualquer compilação.Se você executar o violino e assistir o console, verá o seguinte:
fonte
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.
fonte
Eu descobri uma solução que é relativamente precisa para avaliar quando a inicialização angular está completa.
A diretiva é:
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.
fonte
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
fonte
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.
fonte
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:
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.
fonte
De acordo com a equipe Angular e este problema no Github :
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 :
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.
fonte
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:
HTML de subparcial
fonte
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.
fonte
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
})
Clique aqui para obter os documentos completos - Crédito para K. Scott Allen
fonte
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
o $ compile retorna dados compilados. você pode verificar os dados compilados
fonte