Comment exécuter la fonction dans le contrôleur AngularJS sur un document prêt?


254

J'ai une fonction dans mon contrôleur angulaire, j'aimerais que cette fonction soit exécutée sur un document prêt mais j'ai remarqué que angulaire l'exécute lorsque le dom est créé.

 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
 }

Quelqu'un sait comment je peux m'y prendre?

Réponses:


449

Nous pouvons utiliser la angular.element(document).ready()méthode pour attacher des rappels lorsque le document est prêt. Nous pouvons simplement attacher le rappel dans le contrôleur comme ceci:

angular.module('MyApp', [])

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

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


64
ou vous pouvez utiliser $ document.ready (function () {...}), Angular Docs: docs.angularjs.org/api/ng/service/$document
StuR

29
Vous aurez besoin d'injecter $documentpour l'utiliser. angular.elementfonctionne prêt à l'emploi.
nshew

17
Vous devrez injecter $ document pour l'utiliser, mais c'est la façon recommandée de référencer l'élément document. Vous n'êtes pas obligé, mais cela facilite les tests.
Jason Cox

10
documentest une variable globale où as $documentest injectable par angulaire et facilite ainsi les tests. En général, il est difficile de tester les applications unitaires qui accèdent aux variables JS globales comme le document ou la fenêtre. Je pense aussi que c'est module.runun bon endroit pour mettre ce code à la place du contrôleur.
lanoxx

7
Cela ne fonctionne pas pour moi, l'appel getElementById renvoie null.
ThePartyTurtle

29

Voir cet article Comment exécuter la fonction de contrôleur angulaire lors du chargement de la page?
Pour une recherche rapide:

// 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 cette façon, vous n'avez pas à attendre que le document soit prêt.


1
que faire si j'ai besoin d'appeler après le chargement de la page?
Rishi

22

Angular s'initialise automatiquement lors d'un DOMContentLoadedévénement ou lorsque le script angular.js est évalué si à ce moment document.readyState est défini sur 'complete'. À ce stade, Angular recherche la directive ng-app qui désigne la racine de votre application.

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

Cela signifie que le code du contrôleur s'exécutera une fois le DOM prêt.

C'est donc juste $scope.init().


9
J'ai essayé de le faire, mais curieusement, cela n'a pas fonctionné certaines choses dans ma page n'ont pas été chargées
foreyez

et comment savoir quand le code angulaire est prêt avant d'effectuer des actions comme cliquer sur des boutons, etc. lors de l'écriture de tests Web automatisés?
akostadinov

Est également DOMContentLoadeddéclenché lorsque le document a été complètement chargé et analysé, sans attendre que les feuilles de style, les images et les sous-trames aient fini de se charger. Pour plus d'informations, voir Quelle est la différence entre DOMContentLoaded et les événements de chargement? .
georgeawg

22

Angular a plusieurs points de temps pour commencer à exécuter des fonctions. Si vous cherchez quelque chose comme jQuery

$(document).ready();

Vous pouvez trouver cet analogue en angulaire très utile:

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

Celui-ci est utile lorsque vous souhaitez manipuler les éléments DOM. Il ne démarrera que lorsque tous les éléments seront chargés.

UPD: ce qui est dit ci-dessus fonctionne lorsque vous souhaitez modifier les propriétés css. Cependant, parfois, cela ne fonctionne pas lorsque vous souhaitez mesurer les propriétés des éléments, telles que la largeur, la hauteur, etc. Dans ce cas, vous pouvez essayer ceci:

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

Cela mérite plus que la réponse suggérée. J'ai obtenu ce travail sans utiliser $ timeout.
Richie86

cela ne fonctionne pour moi qu'avec setTimeout (0) - sans lui, le contenu sous une répétition ng par exemple n'est pas encore chargé à ce stade
Ignacio Vazquez

2

J'ai eu une situation similaire où j'avais besoin d'exécuter une fonction de contrôleur après le chargement de la vue et également après le chargement, l'initialisation d'un composant tiers particulier dans la vue et la mise en référence de lui-même sur $ scope. Ce qui a fini par fonctionner pour moi a été de configurer une montre sur cette propriété de portée et de ne déclencher ma fonction qu'après son initialisation.

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

L'observateur est appelé plusieurs fois avec des valeurs non définies. Ensuite, lorsque la grille est créée et possède la propriété attendue, la fonction d'initialisation peut être appelée en toute sécurité. La première fois que l'observateur est appelé avec une newValue non indéfinie, oldValue sera toujours indéfinie.


1

Voici ma tentative à l'intérieur d'un contrôleur externe utilisant coffeescript. Cela fonctionne plutôt bien. Veuillez noter que settings.screen.xs | sm | md | lg sont des valeurs statiques définies dans un fichier non uglified que j'inclus avec l'application. Les valeurs correspondent aux points d'arrêt officiels Bootstrap 3 pour les tailles de requête de média éponyme:

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

1

La réponse

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

est le seul qui fonctionne dans la plupart des scénarios que j'ai testés. Dans une page d'exemple avec 4 composants qui construisent tous du HTML à partir d'un modèle, l'ordre des événements était

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

Ainsi, un $ document.ready () est inutile dans la plupart des cas, car le DOM en cours de construction en angulaire peut être loin d'être prêt.

Mais plus intéressant, même après le tir de $ viewContentLoaded, l'élément d'intérêt n'a toujours pas pu être trouvé.

Ce n'est qu'après l'exécution du délai d'expiration $ qu'il a été trouvé. Notez que même si le délai d'expiration $ était une valeur de 0, près de 200 millisecondes se sont écoulées avant son exécution, ce qui indique que ce thread a été suspendu pendant un certain temps, probablement pendant que le DOM avait des modèles angulaires ajoutés sur un thread principal. Le temps total entre la première exécution de $ document.ready () et la dernière exécution de $ timeout était de près de 500 millisecondes.

Dans un cas extraordinaire où la valeur d'un composant a été définie, puis la valeur text () a été modifiée plus tard dans le délai d'expiration $, la valeur $ timeout a dû être augmentée jusqu'à ce qu'elle fonctionne (même si l'élément pouvait être trouvé pendant le délai d'expiration $ ). Quelque chose d'async dans le composant tiers a provoqué une valeur sur le texte jusqu'à ce que suffisamment de temps se soit écoulé. Une autre possibilité est $ scope. $ EvalAsync, mais n'a pas été essayée.

Je suis toujours à la recherche de cet événement qui me dit que le DOM s'est complètement installé et peut être manipulé pour que tous les cas fonctionnent. Jusqu'à présent, une valeur de timeout arbitraire est nécessaire, ce qui signifie au mieux qu'il s'agit d'un kludge qui peut ne pas fonctionner sur un navigateur lent. Je n'ai pas essayé les options JQuery comme liveQuery et publication / abonnement qui peuvent fonctionner, mais ne sont certainement pas purement angulaires.


1

Si vous obtenez quelque chose comme getElementById call returns null, c'est probablement parce que la fonction est en cours d'exécution, mais l'ID n'a pas eu le temps de se charger dans le DOM.

Essayez d'utiliser la réponse de Will (vers le haut) avec un délai. Exemple:

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

0

Pourquoi ne pas essayer avec quels documents angulaires mentionnent https://docs.angularjs.org/api/ng/function/angular.element .

angular.element (rappel)

Je l'ai utilisé dans ma fonction $ onInit () {...}.

 var self = this;

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

Cela a fonctionné pour moi.


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.