Evento de envio quando o angular.js terminou o carregamento

Perguntou-se qual é a melhor maneira de detectar o término do carregamento / bootstrapping da página, quando todas as diretivas foram compiladas / vinculadas.

Qualquer evento já está aí? Devo sobrecarregar a function de bootstrap?

Apenas um palpite: por que não olhar como a diretiva ngCloak faz isso? Claramente, a diretiva ngCloak consegue mostrar o conteúdo depois que as coisas foram carregadas. Aposto que olhar para o ngCloak levará à resposta exata …

EDITAR 1 hora depois: Ok, bem, eu olhei para o ngCloak e é muito curto. O que isso obviamente implica é que a function de compilation não será executada até que as expressões {{template}} tenham sido avaliadas (isto é, o template carregado), assim, a boa funcionalidade da diretiva ngCloak.

Meu palpite seria apenas fazer uma diretiva com a mesma simplicidade do ngCloak e, em sua function de compilation, fazer o que você 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 function de compilation será executada assim que o template for compilado (expressões avaliadas e sub-templates carregados).

EDIT, 23 horas depois: Ok, então fiz algumas pesquisas e também fiz minha própria pergunta . A pergunta que fiz foi indiretamente relacionada a essa questão, mas coincidentemente me levou à resposta que resolve essa questão.

A resposta é que você pode criar uma diretiva simples e colocar seu código na function link da diretiva, que (para a maioria dos casos de uso, explicada abaixo) será executada quando o elemento estiver pronto / carregado. Com base na descrição de Josh da ordem em que as funções de compilation e link são executadas ,

se você tiver essa marcação:

Em seguida, o AngularJS criará as diretivas executando as funções da 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 function de “link” direta é um post-link, portanto a function de link de sua diretiva1 externa não será executada até que a function de link da diretiva2 interna seja executada. É por isso que dizemos que é seguro manipular o DOM no post-link. Portanto, em relação à pergunta original, não deve haver problema em acessar o html interno da diretiva filha a partir da function de link da diretiva externa, embora o conteúdo inserido dinamicamente deva ser compilado, como dito acima.

A partir disso, podemos concluir que podemos simplesmente criar 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á triggersdo quando for carregado:

  ... ...  

É simples assim! Basta fazer uma simples diretiva e usá-la. 😉

Você pode personalizá-lo ainda mais para que possa executar uma expressão (ou seja, uma function) adicionando $scope.$eval($attributes.ngElementReady); para isso:

  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:

  ... 
...

Apenas certifique-se de ter suas funções (por exemplo, bodyIsReady e divIsReady) definidas no escopo (no controlador) em que seu elemento vive.

Advertências: Eu disse que isso funcionará na maioria dos casos. Tenha cuidado ao usar determinadas diretivas como ngRepeat e ngIf. Eles criam seu próprio escopo e sua diretiva pode não ser acionada. Por exemplo, se você colocar nossa nova diretiva ngElementReady em um elemento que também tenha ngIf, e a condição do ngIf for false, nossa diretiva ngElementReady não será carregada. Ou, por exemplo, se você colocar nossa nova diretiva ngElementReady em um elemento que também tenha 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:

 

em vez disso:

 

A diretiva ngElementReady será compilada no último exemplo, mas sua function 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 determinados cenários como os acima.

EDIT, alguns minutos depois:

Ah, e para responder completamente a questão, você pode agora $emit ou $broadcast seu evento a partir da expressão ou function que é executada no atributo ng-element-ready . 🙂 Por exemplo:

 
...

EDITAR, ainda mais alguns minutos depois:

A resposta do @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 outras. Portanto, dependendo do seu caso de uso, o app.run pode ser bom, mas não para elementos específicos. Nesse caso, as funções de link são melhores.

EDIT, 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 a contabilidade às suas parciais (por exemplo, uma forma é fazer com que cada parcial acompanhe o carregamento do conteúdo e, em seguida, emitir um evento para que o escopo pai possa contar quantas parciais foram carregadas e finalmente fazer o que precisa depois de todas as parciais são carregadas).

EDIT, 23 de outubro às 22:52 PST:

Fiz uma diretiva simples para triggersr 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); } }); } }; }); 

EDIT, 24 de outubro às 12:48 am PST:

Eu melhorei minha diretiva ngElementReady original e a whenReady para 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); } } } }]); 

Por exemplo, use-o assim para triggersr someFunction quando um elemento é carregado e {{placeholders}} ainda não foi substituído:

 
{{item.property}}

someFunction será chamado antes de todos os item.property reservados item.property serem substituídos.

Avalie quantas expressões desejar e torne a última expressão true a aguardar que {{placeholders}} seja avaliado assim:

 
{{item.property}}

someFunction e anotherFunction serão acionados depois que {{placeholders}} forem substituídos.

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 acontecendo depois que os espaços reservados tiverem sido inicialmente substituídos (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 19:26 PST:

Tudo bem, esta é provavelmente minha última e última atualização. Isso provavelmente funcionará para 99,999 dos casos de uso disponíveis:

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

Use assim

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

Claro, provavelmente pode ser otimizado, mas vou deixar por isso mesmo. requestAnimationFrame é legal.

Nos documentos para angular.Module , há uma input descrevendo a function run :

Use este método para registrar o trabalho que deve ser executado quando o injetor terminar de carregar todos os módulos.

Então, se você tem algum módulo que é seu aplicativo:

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

Você pode executar coisas após os módulos terem sido carregados com:

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

EDIT: boot manual para o resgate

Então, foi apontado que a run não é chamada quando o DOM está pronto e ligado. Ele é chamado quando o $injector para o módulo referenciado pelo ng-app carregou todas as suas dependencies, o que é separado da etapa de compilation do DOM.

Eu dei uma olhada na boot manual , e parece que isso deve funcionar.

Eu fiz um violino para ilustrar .

O HTML é simples:

   This is a test   

Observe a falta de um ng-app . E eu tenho uma diretiva que fará alguma manipulação 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: '

', replace: true, transclude: true, compile: function() { console.log("Compiling test-directive"); return { pre: function() { console.log("Prelink"); }, post: function() { console.log("Postlink"); } }; } }; });

Substituiremos a tag de test-directive por uma div da class test-directive e includeemos seu conteúdo em h1 .

Eu adicionei uma function de compilation que retorna funções de link pré e pós 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 class de test-directive de test-directive no DOM, e depois de terminar, 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')); }); 

É bem direto. 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ê append uma function de run ao módulo do app , verá que ela é executada antes que qualquer uma das compilações ocorra.

Se você executar o violino e assistir ao console, verá o seguinte:

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

Queria adicionar mais uma conclusão que eu tinha sobre a minha própria pergunta: Angular não forneceu uma maneira de sinalizar quando uma página terminou de carregar, talvez porque ‘terminado’ depende do seu aplicativo . por exemplo, se você tem uma tree hierárquica de parciais, uma carregando as outras. ‘terminar’ significaria que todos eles foram carregados. Qualquer framework teria dificuldade em analisar seu código e entender que tudo é feito, ou ainda esperado.Para isso, você teria que fornecer uma lógica específica do aplicativo para verificar e determinar isso.

obrigado

Lior

Eu encontrei uma solução que é relativamente precisa ao avaliar quando a boot 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 ser adicionado como um atributo ao elemento body e, em seguida, escutado usando $scope.$on('initialised', fn)

Ele funciona assumindo que o aplicativo é inicializado quando não há mais ciclos $ digest. $ watch é chamado todo ciclo de digitação e assim um timer é iniciado (setTimeout não $ timeout, portanto, um novo ciclo de digitação não é acionado). Se um ciclo de digitação não ocorrer dentro do tempo limite, presume-se que o aplicativo tenha inicializado.

Obviamente, não é tão preciso quanto a solução satchmoruns (já que é possível que um ciclo de digitação demore mais do que o tempo limite), mas minha solução não precisa de você para acompanhar os módulos, o que facilita muito o gerenciamento (particularmente 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 evento $viewContentLoaded .

“$ viewContentLoaded – acionado quando a view é carregada, depois que o DOM é renderizado . O ‘$ scope’ da view emite o evento.” – Link

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

Eu observo manipulação DOM de angular com JQuery e eu fiz um acabamento para o meu aplicativo (algum tipo de situação pré-definida e satisfatória que eu preciso para o meu app-resumo) por exemplo, eu espero que o meu ng-repetidor para produzir 7 resultado e lá para i irá definir uma function de observação com a ajuda de setInterval para este fim.

 $(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, a prioridade é menor. Mais $ timeout fará com que o Angular seja executado em todo o loop de events antes da execução do retorno de chamada.

$ rootScope usado, porque permite colocar diretivas em qualquer escopo do aplicativo e notificar apenas os ouvintes necessários.

$ rootScope. $ emit irá triggersr um evento para todos os $ rootScope. $ apenas nos ouvintes. A parte interessante é que $ rootScope. $ Broadcast notificará todos os $ rootScope. $ Em, bem como $ scope. $ Em listeners Source

De acordo com a equipe Angular e esta questão do Github :

agora temos os events $ viewContentLoaded e $ includeContentLoaded que são emitidos em ng-view e ng-include, respectivamente. Eu acho que isso é o mais próximo que se pode saber quando terminamos a compilation.

Com base nisso, parece que atualmente não é possível fazer isso de maneira confiável, caso contrário, Angular teria fornecido o evento fora da checkbox.

Inicializar o aplicativo implica em executar o ciclo de digitação no escopo raiz e também não há um evento concluído no ciclo de digitação.

De acordo com os documentos de design do Angular 2:

Devido a vários resumos, é impossível determinar e notificar o componente 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 que isso não é possível é uma das razões pelas quais a decisão foi tomada para uma reescrita em Angular 2.

Eu tinha um fragment que estava sendo carregado após / pela parcial principal que veio via roteamento.

Eu precisava executar uma function depois que subpartial carregado e eu não queria escrever uma nova directiva e descobri que você poderia usar um ngIf insolente

Controlador de parental parcial:

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

HTML de subpartial

  

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 o controlador for carregado.

Além disso, se você quiser reagir quando todas as diretivas forem concluídas, você poderá adicionar as soluções propostas apropriadas acima na lógica de boot.

 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, considero essa solução a mais fácil e a menor quantidade de código necessária. Usando a propriedade ‘resolver’ para aguardar a conclusão de uma promise 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 a documentação completa – Crédito para K. Scott Allen

Pode ser que eu possa ajudá-lo por este exemplo

No fancybox personalizado, 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); } 

a compilation $ retorna dados compilados. você pode verificar os dados compilados