Como executar a função no controlador AngularJS no documento pronto?

254

Eu tenho uma função no meu controlador angular, gostaria que essa função fosse executada em um documento pronto, mas notei que a angular a executa à medida que o dom é criado.

 function myController($scope)
 {
     $scope.init = function()
     {
        // I'd like to run this on document ready
     }

     $scope.init(); // doesn't work, loads my init before the page has completely loaded
 }

Alguém sabe como eu posso fazer isso?

foreyez
fonte

Respostas:

449

Podemos usar o angular.element(document).ready()método para anexar retornos de chamada para quando o documento estiver pronto. Podemos simplesmente anexar o retorno de chamada no controlador da seguinte maneira:

angular.module('MyApp', [])

.controller('MyCtrl', [function() {
    angular.element(document).ready(function () {
        document.getElementById('msg').innerHTML = 'Hello';
    });
}]);

http://jsfiddle.net/jgentes/stwyvq38/1/

Vai
fonte
64
ou você pode usar $ (function () {...}) document.ready, Angular Docs: docs.angularjs.org/api/ng/service/$document
Stur
29
Você precisará injetar $documentpara usá-lo. angular.elementTrabalha fora da caixa.
Nshew 5/09
17
Você precisará injetar $ document para usá-lo, mas é a maneira recomendada de referenciar o elemento do documento. Você não precisa, mas facilita muito os testes.
Jason Cox
10
documenté uma variável global em que, como $documenté injetável por angular, facilita o teste. Em geral, é difícil realizar aplicativos de teste unitário que acessam variáveis ​​JS globais, como documento ou janela. Eu também acho que module.runé um bom lugar para colocar esse código em vez do controlador.
lanoxx
7
Isso não funciona para mim, a chamada getElementById retorna nulo.
ThePartyTurtle
29

Veja este post Como executar a função do controlador angular no carregamento da página?
Para uma pesquisa rápida:

// register controller in html
<div data-ng-controller="myCtrl" data-ng-init="init()"></div>

// in controller
$scope.init = function () {
    // check if there is query in url
    // and fire search in case its value is not empty
};

Dessa forma, você não precisa esperar até que o documento esteja pronto.

vinha
fonte
1
e se eu precisar ligar após o carregamento da página?
Rishi
22

Angular é inicializado automaticamente após o DOMContentLoadedevento ou quando o script angular.js é avaliado se naquele momento document.readyState estiver definido como 'complete'. Neste ponto, o Angular procura a diretiva ng-app que designa a raiz do seu aplicativo.

https://docs.angularjs.org/guide/bootstrap

Isso significa que o código do controlador será executado depois que o DOM estiver pronto.

Assim é justo $scope.init().

Will M
fonte
9
Eu tentei fazer isso, mas estranhamente não funcionou algumas coisas na minha página não foram carregados
foreyez
e como saber quando o código angular está pronto antes de executar ações como clicar nos botões etc. ao escrever testes da web automatizados?
akostadinov 27/01
Também DOMContentLoadedé acionado quando o documento foi completamente carregado e analisado, sem aguardar folhas de estilo, imagens e sub-quadros para concluir o carregamento. Para obter mais informações, consulte Qual é a diferença entre DOMContentLoaded e carregar eventos? .
Georgeawg
22

Angular tem vários pontos no tempo para começar a executar funções. Se você procura algo como o jQuery

$(document).ready();

Você pode achar esse analógico em angular muito útil:

$scope.$watch('$viewContentLoaded', function(){
    //do something
});

Este é útil quando você deseja manipular os elementos DOM. Ele começará a executar somente depois que todos os elementos te forem carregados.

UPD: O que foi dito acima funciona quando você deseja alterar as propriedades do css. No entanto, às vezes não funciona quando você deseja medir as propriedades do elemento, como largura, altura, etc. Nesse caso, você pode tentar o seguinte:

$scope.$watch('$viewContentLoaded', 
    function() { 
        $timeout(function() {
            //do something
        },0);    
});
Alex Link
fonte
Isso merece mais do que a resposta sugerida. Consegui esse trabalho sem usar $ timeout.
Richie86
ele trabalha para mim apenas com setTimeout (0) - sem ela conteúdos sob uma repetição ng por exemplo, não são carregados ainda neste ponto
Ignacio Vazquez
2

Eu tive uma situação semelhante em que precisei executar uma função de controlador após o carregamento da exibição e também depois que um componente de terceiros específico da exibição foi carregado, inicializado e colocou uma referência a si mesmo no $ scope. O que acabou funcionando para mim foi configurar um relógio nessa propriedade de escopo e disparar minha função somente depois que ela foi inicializada.

// $scope.myGrid property will be created by the grid itself
// The grid will have a loadedRows property once initialized

$scope.$watch('myGrid', function(newValue, oldValue) {
    if (newValue && newValue.loadedRows && !oldValue) {
        initializeAllTheGridThings();
    }
});

O observador é chamado algumas vezes com valores indefinidos. Então, quando a grade é criada e possui a propriedade esperada, a função de inicialização pode ser chamada com segurança. Na primeira vez que o observador é chamado com um newValue não indefinido, oldValue ainda será indefinido.

MikeyM
fonte
1

Aqui está minha tentativa dentro de um controlador externo usando coffeescript. Funciona bastante bem. Observe que settings.screen.xs | sm | md | lg são valores estáticos definidos em um arquivo não uglificado que eu incluo no aplicativo. Os valores são de acordo com os pontos de interrupção oficiais do Bootstrap 3 para os tamanhos de consulta de mídia de mesmo nome:

xs = settings.screen.xs // 480
sm = settings.screen.sm // 768
md = settings.screen.md // 992
lg = settings.screen.lg // 1200

doMediaQuery = () ->

    w = angular.element($window).width()

    $scope.xs = w < sm
    $scope.sm = w >= sm and w < md
    $scope.md = w >= md and w < lg
    $scope.lg = w >= lg
    $scope.media =  if $scope.xs 
                        "xs" 
                    else if $scope.sm
                        "sm"
                    else if $scope.md 
                        "md"
                    else 
                        "lg"

$document.ready () -> doMediaQuery()
angular.element($window).bind 'resize', () -> doMediaQuery()
akutz
fonte
1

A resposta

$scope.$watch('$viewContentLoaded', 
    function() { 
        $timeout(function() {
            //do something
        },0);    
});

é o único que funciona na maioria dos cenários que testei. Em uma página de amostra com 4 componentes, todos construindo HTML a partir de um modelo, a ordem dos eventos foi

$document ready
$onInit
$postLink
(and these 3 were repeated 3 more times in the same order for the other 3 components)
$viewContentLoaded (repeated 3 more times)
$timeout execution (repeated 3 more times)

Portanto, um $ document.ready () é inútil na maioria dos casos, já que o DOM sendo construído em angular pode estar nem perto de pronto.

Porém, mais interessante, mesmo depois que o $ viewContentLoaded foi acionado, o elemento de interesse ainda não foi encontrado.

Somente após o $ timeout executado foi encontrado. Observe que, embora o tempo limite de $ tenha sido o valor 0, quase 200 milissegundos transcorreram antes de ser executado, indicando que esse encadeamento foi suspenso por um bom tempo, presumivelmente enquanto o DOM tinha modelos angulares adicionados em um encadeamento principal. O tempo total desde o primeiro $ document.ready () até a última execução do timeout $ foi de quase 500 milissegundos.

Em um caso extraordinário em que o valor de um componente foi definido e o valor de text () foi alterado posteriormente no tempo limite de $, o valor de tempo limite de $ teve que ser aumentado até que funcionasse (mesmo que o elemento pudesse ser encontrado durante o tempo limite de $ ) Algo assíncrono no componente de terceiros fez com que um valor tivesse precedência sobre o texto até que passasse tempo suficiente. Outra possibilidade é $ scope. $ EvalAsync, mas não foi tentada.

Ainda estou procurando aquele evento que me diz que o DOM se estabeleceu completamente e pode ser manipulado para que todos os casos funcionem. Até o momento, é necessário um valor de tempo limite arbitrário, o que significa, na melhor das hipóteses, que este é um argumento que pode não funcionar em um navegador lento. Eu não tentei opções JQuery como liveQuery e publique / assine, o que pode funcionar, mas certamente não é totalmente angular.

Wray Smallwood
fonte
1

Se você estiver obtendo algo assim getElementById call returns null, provavelmente é porque a função está em execução, mas o ID não teve tempo de carregar no DOM.

Tente usar a resposta de Will (na parte superior) com um atraso. Exemplo:

angular.module('MyApp', [])

.controller('MyCtrl', [function() {
    $scope.sleep = (time) => {
        return new Promise((resolve) => setTimeout(resolve, time));
    };
    angular.element(document).ready(function () {
        $scope.sleep(500).then(() => {        
            //code to run here after the delay
        });
    });
}]);
Senhor Verde
fonte
0

Por que não tentar com o que os documentos angulares mencionam https://docs.angularjs.org/api/ng/function/angular.element .

angular.element (retorno de chamada)

Eu usei isso dentro da minha função $ onInit () {...}.

 var self = this;

 angular.element(function () {
        var target = document.getElementsByClassName('unitSortingModule');
        target[0].addEventListener("touchstart", self.touchHandler, false);
        ...
    });

Isso funcionou para mim.

Mahib
fonte
0
$scope.$on('$ViewData', function(event) {
//Your code.
});
Shreyansh Kumar
fonte