170

I have a function which I want to call after page content is loaded. I read about $viewContentLoaded and it doesn't work for me. I am looking for something like

document.addEventListener('DOMContentLoaded', function () { 
     //Content goes here 
}, false);

Above call doesn't work for me in AngularJs controller.

Gaurav Gandhi
  • 2,536
  • 2
  • 23
  • 35
user3233772
  • 1,817
  • 3
  • 11
  • 6

15 Answers15

164

According to documentation of $viewContentLoaded, it supposed to work

Emitted every time the ngView content is reloaded.

$viewContentLoaded event is emitted that means to receive this event you need a parent controller like

<div ng-controller="MainCtrl">
  <div ng-view></div>
</div>

From MainCtrl you can listen the event

  $scope.$on('$viewContentLoaded', function(){
    //Here your view content is fully loaded !!
  });

Check the Demo

Ryan Haining
  • 30,835
  • 10
  • 95
  • 145
Tasnim Reza
  • 5,959
  • 3
  • 22
  • 29
  • 59
    The problem with this approach is that controllers have not fully initialized at this point. – Ash Blue Jun 17 '14 at 08:59
  • @AshBlue what do mean by "Controllers have not fully initialized" ? Here at first initialize `MainCntl` then `BookCntl` after that `emit $viewContentLoaded` event. – Tasnim Reza May 31 '15 at 05:28
  • 12
    @Reza He is correct, this event is fired when the dom is added but before angular has finished processing the dom. You can test this by adding a id or class as angular variable and try to find it in $viewContentLoaded with jQuery. You will not find it. – Thomas Kekeisen Jun 11 '15 at 06:51
  • 1
    @Reza yes,Thomas is right the form element cannot access inside the $viewContentLoaded say,$scope.formName.elementName.$setValidity("validateRule", false); will throw "TypeError: Cannot read property 'elementName' of undefined" – Mussammil Oct 14 '15 at 06:04
  • 2
    The same can be called on $rootScope level. Instead of $scope.$on, it should be $rootScope.$on and inside of app.run() block – Vil Apr 20 '16 at 21:32
  • i use this and worked for disappearing loading icon div icon. http://stackoverflow.com/a/15463124/308578 – saber tabatabaee yazdi Mar 04 '17 at 13:08
  • 4
    This option will not work when you have `ng-repeat` and several nested directives that will generate HTML/Content based on complex loops and conditions. Rendering continues for some time, even after `$viewContentLoaded` event is triggered. How you can ensure that all elements have been fully rendered then execute certain code? Any feedback? – tarekahf Oct 11 '17 at 14:21
  • @tarekahf It is hard to give feedback without code or example. For framework it is really hard to know when your rendering will be finished, since some element may render as async. So you've to figure it out at which point you need to call the function. It would be nice if can you ask new question with your complex scenario or give some example or plunker! – Tasnim Reza Oct 11 '17 at 21:40
  • @Reza I think I found a solution, please check it out and let me know what you think: https://stackoverflow.com/a/46692965/4180447 – tarekahf Oct 11 '17 at 21:58
110

Angular < 1.6.X

angular.element(document).ready(function () {
    console.log('page loading completed');
});

Angular >= 1.6.X

angular.element(function () {
    console.log('page loading completed');
});
hariszaman
  • 7,457
  • 2
  • 35
  • 53
76

fixed - 2015.06.09

Use a directive and the angular element ready method like so:

js

.directive( 'elemReady', function( $parse ) {
   return {
       restrict: 'A',
       link: function( $scope, elem, attrs ) {    
          elem.ready(function(){
            $scope.$apply(function(){
                var func = $parse(attrs.elemReady);
                func($scope);
            })
          })
       }
    }
})

html

<div elem-ready="someMethod()"></div>

or for those using controller-as syntax...

<div elem-ready="vm.someMethod()"></div>

The benefit of this is that you can be as broad or granular w/ your UI as you like and you are removing DOM logic from your controllers. I would argue this is the recommended Angular way.

You may need to prioritize this directive in case you have other directives operating on the same node.

Community
  • 1
  • 1
jusopi
  • 6,433
  • 2
  • 32
  • 42
  • this looks like a really good solution, but I couldn't make it work. it choked at `elem.ready( $scope.$apply( readyFunc( $scope )));` Any ideas why that might be? Perhaps an update to angular happened? Anyway - it's an elegant approach, if it could work. – domoarigato Jun 08 '15 at 19:37
  • 1
    nm, I see what the issue was. There was a typo on the `attrs.onReady`, should be what it is now. The other issue was that I was calling it funky.... ... what I get for converting coffeescript from memory to JS. – jusopi Jun 09 '15 at 18:53
  • worked for me too - thanks! I had to invoke it without the $scope in the html though, like this: `
    ` For those coming after, this is a great way to run specific code after a template has been loaded, in my case, I needed to initialize popovers in bootstrap, but was having trouble doing it after that specific template had been loaded.
    – domoarigato Jun 10 '15 at 05:50
  • @domoarrigato I think that was my way of indicating the scope method. I normally use *controller-as* syntax. I will correct to limit confusion. – jusopi Jun 10 '15 at 13:50
  • FYI, blue's answer does the same thing without the directive - any reason you can think of that this might be preferable? In my use case, both worked. – domoarigato Jun 10 '15 at 15:01
  • 1
    I think in certain cases that may be fine, but my arguments against it are: I think the expectations of his code when just glancing is that that would return some text value to display in the DOM,ergo it requires a careful eye; It doesn't allow any timing in terms of directive priority, you are stuck at the controller level unless you use `$timeout`; You're explicitly creating an addt. DOM node simply to execute non-dom logic; It's only reusable if that method resides far up in the $scope hierarchy such that child scopes inherit it;I just think it looks bad in an NG perspective; – jusopi Jun 10 '15 at 16:09
  • 5
    Worked for me after adding a `$timeout(function() {})` around the `elem.ready()`. Otherwise it was throwing a `$digest already in progress` error. But anyway, thanks a ton! I have been struggling with this for the past hour. – rkrishnan May 11 '16 at 16:54
  • I have this directive but I'm unable to access any functions in anything. Does it matter if the attibute is inside a ng-controller, I tried adding the function I'm calling but it can't see it... – MiloTheGreat Nov 04 '16 at 12:05
  • 1
    Digging through the $scope appears to be empty. Any ideas? – MiloTheGreat Nov 04 '16 at 12:17
  • Is it possible to pass the element to the method called when calling the method on a controller? – Xtroce Jan 30 '19 at 13:58
68

You can directly call it by adding {{YourFunction()}} after HTML element.

Here is a Plunker Link.

BaCaRoZzo
  • 7,022
  • 6
  • 44
  • 72
blue
  • 914
  • 7
  • 9
  • 6
    Could you please elaborate more your answer adding a little more description about the solution you provide? – abarisone May 22 '15 at 12:35
  • I think you want to call a function when that html element is loaded. So call that function as mentioned above. If my guess is right about your doubt then this will work otherwise please elaborate your question. :) – blue May 24 '15 at 14:19
  • This also works, even without the directive that is described in jusopi's answer. I simply added {{YourFunction()}}, which is a function from the scopes controller, inside the tag in question (in my case, a div) - I think no further elaboration is needed. – domoarigato Jun 10 '15 at 14:59
  • 14
    When calling a function like this. Make sure its invoked once or otherwise the digest cycle will run infinite and shows an error – Ashan Aug 03 '15 at 01:55
  • 2
    i like this solution, it is realy so simple – Kamuran Sönecek Dec 29 '15 at 08:09
  • You are correct @Ashan. Need to add some condition within the function otherwise it will execute whenever the DOM will update. – blue Apr 28 '16 at 09:34
  • Sorry- I thought this didn't work as expected.. but actually the simplest way to call a function after the `ng-repeat` has been updated - even from dynamic filters. Thanks +1 – Piotr Kula Mar 15 '17 at 14:39
  • 1
    This is fantastic - only one problem. It appears that my function runs 5 times when I use this. I added a counter so it's working fine but I'm just wondering if anyone knows why that would occur... Seems a bit strange you know? – itchyspacesuit May 07 '17 at 05:09
  • After days of ripping my hair out I stumble across this simple solution that is the only one that works for me! Thanks – DaveOz Jun 14 '17 at 14:13
  • Just amazing! I'm bout to die laughing :'D – Khateeb321 Feb 14 '18 at 10:46
  • This is absolutely awful... in how awesome it is, how simple it is, and how so many of us never even thought of it. – PKD Jul 27 '18 at 23:57
  • This deserves an Oscar – Marcos Brigante Aug 26 '19 at 21:13
  • 1
    This causes infinite loops and should not be used. Use `
    ` as shown in [Saad's answer](https://stackoverflow.com/a/35649046/1843673)
    – Moses Machua May 03 '20 at 20:54
23

I had to implement this logic while handling with google charts. what i did was that at the end of my html inside controller definition i added.

  <body>
         -- some html here -- 
--and at the end or where ever you want --
          <div ng-init="FunCall()"></div>
    </body>

and in that function simply call your logic.

$scope.FunCall = function () {
        alert("Called");
}
Saad Zulfiqar
  • 366
  • 2
  • 14
  • I was using this but at the top of the page, the init required some div to be loaded already so by moving the ng-init to the bottom of the page fixed my issue, nice tip and it makes sense. – Gurnard Aug 24 '16 at 08:37
  • only answer that helped me with my problem (jquery mobile empty selection) – kaiser Oct 28 '18 at 02:36
  • 1
    This is the real answer as it adapts to dynamically loaded controls and is super easy to implement. – King Friday Jan 24 '19 at 17:54
  • this answer saved me some time, so elegant and effective for my use case – Sello Nov 14 '19 at 13:51
4
var myM = angular.module('data-module');

myM.directive('myDirect',['$document', function( $document ){

    function link( scope , element , attrs ){

        element.ready( function(){

        } );

        scope.$on( '$viewContentLoaded' , function(){

            console.log(" ===> Called on View Load ") ;

        } );

    }

    return {
        link: link
    };

}] );

Above method worked for me

Alok D
  • 73
  • 3
3

you can call javascript version of onload event in angular js. this ng-load event can be applied to any dom element like div, span, body, iframe, img etc. following is the link to add ng-load in your existing project.

download ng-load for angular js

Following is example for iframe, once it is loaded testCallbackFunction will be called in controller

EXAMPLE

JS

    // include the `ngLoad` module
    var app = angular.module('myApp', ['ngLoad']);
    app.controller('myCtrl', function($scope) {
        $scope.testCallbackFunction = function() {
          //TODO : Things to do once Element is loaded
        };

    });  

HTML

  <div ng-app='myApp' ng-controller='myCtrl'> 
      <iframe src="test.html" ng-load callback="testCallbackFunction()">  
  </div>
mtpultz
  • 13,197
  • 18
  • 96
  • 180
Darshan Jain
  • 306
  • 2
  • 9
2

If you're getting a $digest already in progress error, this might help:

return {
        restrict: 'A',
        link: function( $scope, elem, attrs ) {
            elem.ready(function(){

                if(!$scope.$$phase) {
                    $scope.$apply(function(){
                        var func = $parse(attrs.elemReady);
                        func($scope);
                    })
                }
                else {

                    var func = $parse(attrs.elemReady);
                    func($scope);
                }

            })
        }
    }
John Smith
  • 1,861
  • 3
  • 11
  • 14
2

I was using {{myFunction()}} in the template but then found another way here using $timeout inside the controller. Thought I'd share it, works great for me.

angular.module('myApp').controller('myCtrl', ['$timeout',
function($timeout) {
var self = this;

self.controllerFunction = function () { alert('controller function');}

$timeout(function () {
    var vanillaFunction = function () { alert('vanilla function'); }();
    self.controllerFunction();
});

}]);
Community
  • 1
  • 1
Nhan
  • 1,215
  • 13
  • 15
  • I tried all the answers here and for some reason, $timeout is the only thing that worked for me. Not sure why, but it could have something to do with ng-include and the timing of initiating services. – Drellgor Nov 13 '17 at 22:56
  • I added a plus one for {{myFunction()}} – commonpike May 08 '18 at 12:13
0

Running after the page load should partially be satisfied by setting an event listener to the window load event

window.addEventListener("load",function()...)

Inside the module.run(function()...) of angular you will have all access to the module structure and dependencies.

You can broadcast and emit events for communications bridges.

For example:

  • module set onload event and build logic
  • module broadcast event to controllers when logic required it
  • controllers will listen and execute their own logic based on module onload processes.
Tristan
  • 3,298
  • 8
  • 20
  • 27
0

If you want certain element to completely loaded, Use ng-init on that element .

e.g. <div class="modal fade" id="modalFacultyInfo" role="dialog" ng-init="initModalFacultyInfo()"> ..</div>

the initModalFacultyInfo() function should exist in the controller.

NetDeveloper
  • 469
  • 1
  • 8
  • 20
0

I found that if you have nested views - $viewContentLoaded gets triggered for every of the nested views. I've created this workaround to find the final $viewContentLoaded. Seems to work alright for setting $window.prerenderReady as required by Prerender (goes into .run() in the main app.js):

// Trigger $window.prerenderReady once page is stable
// Note that since we have nested views - $viewContentLoaded is fired multiple
// times and we need to go around this problem
var viewContentLoads = 0;
var checkReady = function(previousContentLoads) {
  var currentContentLoads = Number(viewContentLoads) + 0; // Create a local copy of the number of loads
  if (previousContentLoads === currentContentLoads) { // Check if we are in a steady state
    $window.prerenderReady = true; // Raise the flag saying we are ready
  } else {
    if ($window.prerenderReady || currentContentLoads > 20) return; // Runaway check
    $timeout(function() {checkReady(currentContentLoads);}, 100); // Wait 100ms and recheck
  }
};
$rootScope.$on('$stateChangeSuccess', function() {
  checkReady(-1); // Changed the state - ready to listen for end of render
});
$rootScope.$on('$viewContentLoaded', function() {
  viewContentLoads ++;
});
Maksym
  • 1,080
  • 1
  • 7
  • 13
0
var myTestApp = angular.module("myTestApp", []); 
myTestApp.controller("myTestController", function($scope, $window) {
$window.onload = function() {
 alert("is called on page load.");
};
});
  • While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. – rollstuhlfahrer Feb 23 '18 at 10:21
0

The solution that work for me is the following

app.directive('onFinishRender', ['$timeout', '$parse', function ($timeout, $parse) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
                $timeout(function () {
                    scope.$emit('ngRepeatFinished');
                    if (!!attr.onFinishRender) {
                        $parse(attr.onFinishRender)(scope);
                    }
                });
            }

            if (!!attr.onStartRender) {
                if (scope.$first === true) {
                    $timeout(function () {
                        scope.$emit('ngRepeatStarted');
                        if (!!attr.onStartRender) {
                            $parse(attr.onStartRender)(scope);
                        }
                    });
                }
            }
        }
    }
}]);

Controller code is the following

$scope.crearTooltip = function () {
     $('[data-toggle="popover"]').popover();
}

Html code is the following

<tr ng-repeat="item in $data" on-finish-render="crearTooltip()">
Jorge Santos Neill
  • 1,089
  • 7
  • 6
-31

I use setInterval to wait for the content loaded. I hope this can help you to solve that problem.

var $audio = $('#audio');
var src = $audio.attr('src');
var a;
a = window.setInterval(function(){
    src = $audio.attr('src');
    if(src != undefined){
        window.clearInterval(a);
        $('audio').mediaelementplayer({
            audioWidth: '100%'
        });
    }
}, 0);
Frank van Wijk
  • 3,065
  • 18
  • 39
xiangry
  • 19
  • 3