262

I have a function within my angular controller, I'd like this function to be run on document ready but I noticed that angular runs it as the dom is created.

 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
 }

Anyone know how I can go about this?

Paul Rooney
  • 20,879
  • 9
  • 40
  • 61
Shai UI
  • 50,568
  • 73
  • 204
  • 309

10 Answers10

456

We can use the angular.element(document).ready() method to attach callbacks for when the document is ready. We can simply attach the callback in the controller like so:

angular.module('MyApp', [])

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

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

Will
  • 7,225
  • 2
  • 23
  • 14
  • 64
    or you can use $document.ready(function(){...}), Angular Docs: https://docs.angularjs.org/api/ng/service/$document – StuR Jul 04 '14 at 10:18
  • 29
    You'll need to inject `$document` to use it. `angular.element` works out-of-the-box. – nshew13 Sep 05 '14 at 17:43
  • 17
    You will need to inject $document to use it, but it is the recommended way to reference the document element. You don't have to, but it makes testing way easier. – Jason Cox Oct 01 '14 at 01:29
  • 10
    `document` is a global variable where as `$document` is injectable by angular and thus makes testing easier. In general is hard to unit test applications that access global JS variables like document or window. I also think that `module.run` is a good place to put this code instead of the controller. – lanoxx Dec 01 '14 at 14:03
  • 1
    I tested this by providing an `alert()` like `angular.element(document).ready(function() { alert("foo");});`, but the alert showed up twice, any specific reason for this? – Sajib Acharya Aug 21 '16 at 18:52
  • @SajibAcharya most likely your controller gets instantiated twice. Same happens for console.log('stuff'); in the controller? – Jonas Eriksson Nov 23 '16 at 10:11
  • 7
    This does not work for me, the getElementById call returns null. – ThePartyTurtle Dec 01 '16 at 18:08
  • AngularJS automatically uses [`ready()`](http://api.jquery.com/ready/) when it bootstraps. Putting `ready()` in a controller construction function will not add any more delay. For more information, see [AngularJS Developer Guide - Bootstrap Automatic Initialization](https://docs.angularjs.org/guide/bootstrap#automatic-initialization). – georgeawg Dec 13 '18 at 13:12
29

See this post How to execute angular controller function on page load?
For fast lookup:

// 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
};

This way, You don't have to wait till document is ready.

Community
  • 1
  • 1
vinesh
  • 4,745
  • 6
  • 41
  • 45
29

Angular has several timepoints to start executing functions. If you seek for something like jQuery's

$(document).ready();

You may find this analog in angular to be very useful:

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

This one is helpful when you want to manipulate the DOM elements. It will start executing only after all te elements are loaded.

UPD: What is said above works when you want to change css properties. However, sometimes it doesn't work when you want to measure the element properties, such as width, height, etc. In this case you may want to try this:

$scope.$watch('$viewContentLoaded', 
    function() { 
        $timeout(function() {
            //do something
        },0);    
});
Alex Link
  • 1,210
  • 13
  • 16
22

Angular initializes automatically upon DOMContentLoaded event or when the angular.js script is evaluated if at that time document.readyState is set to 'complete'. At this point Angular looks for the ng-app directive which designates your application root.

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

This means that the controller code will run after the DOM is ready.

Thus it's just $scope.init().

Gajus
  • 69,002
  • 70
  • 275
  • 438
Will M
  • 2,135
  • 4
  • 22
  • 32
  • 9
    I tried to do that but oddly it didn't work some things in my page weren't loaded – Shai UI Sep 05 '13 at 22:21
  • and how to know when angular code it ready before performing actions like clicking buttons etc. when writing aotomated web tests? – akostadinov Jan 27 '17 at 12:49
  • Also `DOMContentLoaded` is fired when the document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. For more information, see [What is the difference between DOMContentLoaded and load events?](https://stackoverflow.com/questions/2414750/difference-between-domcontentloaded-and-load-events). – georgeawg Dec 13 '18 at 13:04
4

The answer

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

is the only one that works in most scenarios I tested. In a sample page with 4 components all of which build HTML from a template, the order of events was

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

So a $document.ready() is useless in most cases since the DOM being constructed in angular may be nowhere near ready.

But more interesting, even after $viewContentLoaded fired, the element of interest still could not be found.

Only after the $timeout executed was it found. Note that even though the $timeout was a value of 0, nearly 200 milliseconds elapsed before it executed, indicating that this thread was held off for quite a while, presumably while the DOM had angular templates added on a main thread. The total time from the first $document.ready() to the last $timeout execution was nearly 500 milliseconds.

In one extraordinary case where the value of a component was set and then the text() value was changed later in the $timeout, the $timeout value had to be increased until it worked (even though the element could be found during the $timeout). Something async within the 3rd party component caused a value to take precedence over the text until sufficient time passed. Another possibility is $scope.$evalAsync, but was not tried.

I am still looking for that one event that tells me the DOM has completely settled down and can be manipulated so that all cases work. So far an arbitrary timeout value is necessary, meaning at best this is a kludge that may not work on a slow browser. I have not tried JQuery options like liveQuery and publish/subscribe which may work, but certainly aren't pure angular.

2

I had a similar situation where I needed to execute a controller function after the view was loaded and also after a particular 3rd-party component within the view was loaded, initialized, and had placed a reference to itself on $scope. What ended up working for me was to setup a watch on this scope property and firing my function only after it was initialized.

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

The watcher is called a couple of times with undefined values. Then when the grid is created and has the expected property, the initialization function may be safely called. The first time the watcher is called with a non-undefined newValue, oldValue will still be undefined.

MikeyM
  • 78
  • 6
1

Here's my attempt inside of an outer controller using coffeescript. It works rather well. Please note that settings.screen.xs|sm|md|lg are static values defined in a non-uglified file I include with the app. The values are per the Bootstrap 3 official breakpoints for the eponymous media query sizes:

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
  • 355
  • 4
  • 5
1

If you're getting something like getElementById call returns null, it's probably because the function is running, but the ID hasn't had time to load in the DOM.

Try using Will's answer (towards the top) with a delay. Example:

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
        });
    });
}]);
Liam
  • 27,717
  • 28
  • 128
  • 190
Mr. Green
  • 83
  • 1
  • 5
0

Why not try with what angular docs mention https://docs.angularjs.org/api/ng/function/angular.element.

angular.element(callback)

I've used this inside my $onInit(){...} function.

 var self = this;

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

This worked for me.

Mahib
  • 3,977
  • 5
  • 53
  • 62
0
$scope.$on('$ViewData', function(event) {
//Your code.
});