¿Cómo ejecutar la función en el controlador AngularJS en documento listo?

254

Tengo una función dentro de mi controlador angular, me gustaría que esta función se ejecute en un documento listo, pero noté que angular la ejecuta a medida que se crea el dom.

 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
 }

Alguien sabe cómo puedo hacer esto?

foreyez
fuente

Respuestas:

449

Podemos usar el angular.element(document).ready()método para adjuntar devoluciones de llamada para cuando el documento esté listo. Simplemente podemos adjuntar la devolución de llamada en el controlador de la siguiente manera:

angular.module('MyApp', [])

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

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

Será
fuente
64
o puede usar $ document.ready (function () {...}), documentos angulares: docs.angularjs.org/api/ng/service/$document
StuR
29
Tendrás que inyectar $documentpara usarlo. angular.elementfunciona fuera de la caja.
nshew 05 de
17
Deberá inyectar $ document para usarlo, pero es la forma recomendada de hacer referencia al elemento del documento. No tiene que hacerlo, pero facilita mucho las pruebas.
Jason Cox
10
documentes una variable global donde $documentes inyectable por angular y, por lo tanto, facilita las pruebas. En general, es difícil unir las aplicaciones de prueba que acceden a variables JS globales como documentos o ventanas. También creo que module.runes un buen lugar para poner este código en lugar del controlador.
lanoxx
77
Esto no funciona para mí, la llamada getElementById devuelve nulo.
ThePartyTurtle
29

Ver esta publicación ¿Cómo ejecutar la función del controlador angular en la carga de la página?
Para una búsqueda 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
};

De esta manera, no tiene que esperar hasta que el documento esté listo.

vinesh
fuente
1
¿Qué pasa si necesito llamar después de cargar la página?
Rishi
22

Angular se inicializa automáticamente en DOMContentLoadedcaso de evento o cuando se evalúa el script angular.js si en ese momento document.readyState está configurado como 'completo'. En este punto, Angular busca la directiva ng-app que designa la raíz de su aplicación.

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

Esto significa que el código del controlador se ejecutará después de que el DOM esté listo.

Por lo tanto, es justo $scope.init().

Will M
fuente
99
Traté de hacer eso, pero curiosamente no funcionó, algunas cosas en mi página no estaban cargadas
foreyez 05 de
¿y cómo saber cuándo está listo el código angular antes de realizar acciones como hacer clic en botones, etc. al escribir pruebas web automatizadas?
akostadinov
También DOMContentLoadedse activa cuando el documento se ha cargado y analizado por completo, sin esperar a que las hojas de estilo, las imágenes y los subtramas terminen de cargarse. Para obtener más información, consulte ¿Cuál es la diferencia entre DOMContentLoaded y los eventos de carga? .
georgeawg
22

Angular tiene varios puntos de tiempo para comenzar a ejecutar funciones. Si buscas algo como jQuery's

$(document).ready();

Puede encontrar este análogo en angular para ser muy útil:

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

Este es útil cuando desea manipular los elementos DOM. Comenzará a ejecutarse solo después de que se carguen todos los elementos te.

UPD: lo dicho anteriormente funciona cuando desea cambiar las propiedades de CSS. Sin embargo, a veces no funciona cuando desea medir las propiedades del elemento, como ancho, alto, etc. En este caso, puede intentar esto:

$scope.$watch('$viewContentLoaded', 
    function() { 
        $timeout(function() {
            //do something
        },0);    
});
Alex Link
fuente
Esto merece más que la respuesta sugerida. Obtuve este trabajo sin usar $ timeout.
Richie86
solo funciona para mí con setTimeout (0); sin él, el contenido bajo una repetición ng, por ejemplo, no está cargado todavía en este momento
Ignacio Vazquez
2

Tuve una situación similar en la que necesitaba ejecutar una función de controlador después de cargar la vista y también después de que un componente de terceros en particular dentro de la vista se cargó, se inicializó y se colocó una referencia a sí mismo en $ scope. Lo que terminó funcionando para mí fue configurar un reloj en esta propiedad de alcance y activar mi función solo después de que se inicializara.

// $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();
    }
});

El observador se llama un par de veces con valores indefinidos. Luego, cuando se crea la cuadrícula y tiene la propiedad esperada, la función de inicialización se puede llamar de forma segura. La primera vez que se llama al observador con un newValue no definido, oldValue seguirá sin definirse.

MikeyM
fuente
1

Aquí está mi intento dentro de un controlador externo usando coffeescript. Funciona bastante bien Tenga en cuenta que settings.screen.xs | sm | md | lg son valores estáticos definidos en un archivo no uglified que incluyo con la aplicación. Los valores corresponden a los puntos de corte oficiales de Bootstrap 3 para los tamaños de consulta de medios homónimos:

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
fuente
1

La respuesta

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

es el único que funciona en la mayoría de los escenarios que probé. En una página de muestra con 4 componentes que construyen HTML a partir de una plantilla, el orden de los eventos fue

$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)

Por lo tanto, un $ document.ready () es inútil en la mayoría de los casos, ya que el DOM que se construye en angular puede no estar listo para nada.

Pero más interesante, incluso después de que $ viewContentLoaded se disparó, el elemento de interés aún no se pudo encontrar.

Solo después de que se ejecutó el tiempo de espera $ se encontró. Tenga en cuenta que a pesar de que el tiempo de espera $ era un valor de 0, transcurrieron casi 200 milisegundos antes de ejecutarse, lo que indica que este subproceso se suspendió durante bastante tiempo, presumiblemente mientras el DOM tenía plantillas angulares agregadas en un subproceso principal. El tiempo total desde el primer $ document.ready () hasta la última ejecución de $ timeout fue de casi 500 milisegundos.

En un caso extraordinario en el que se estableció el valor de un componente y luego el valor text () se cambió más tarde en el tiempo de espera $, el valor de tiempo de espera tuvo que incrementarse hasta que funcionó (aunque el elemento se pudo encontrar durante el tiempo de espera $ ) Algo asíncrono dentro del componente de terceros hizo que un valor tuviera prioridad sobre el texto hasta que transcurriera el tiempo suficiente. Otra posibilidad es $ scope. $ EvalAsync, pero no se intentó.

Todavía estoy buscando ese evento que me dice que el DOM se ha establecido por completo y puede ser manipulado para que todos los casos funcionen. Hasta ahora es necesario un valor de tiempo de espera arbitrario, lo que significa que en el mejor de los casos es un error que puede no funcionar en un navegador lento. No he probado las opciones de JQuery como liveQuery y publicar / suscribir, que pueden funcionar, pero ciertamente no son angulares.

Wray Smallwood
fuente
1

Si obtiene algo como getElementById call returns nullesto, probablemente sea porque la función se está ejecutando, pero la ID no ha tenido tiempo de cargarse en el DOM.

Intenta usar la respuesta de Will (hacia arriba) con retraso. Ejemplo:

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
        });
    });
}]);
SR verde
fuente
0

¿Por qué no probar con lo que los documentos angulares mencionan https://docs.angularjs.org/api/ng/function/angular.element ?

angular.element (devolución de llamada)

Lo he usado dentro de mi función $ onInit () {...}.

 var self = this;

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

Esto funcionó para mí.

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