32

I am building a web application using Angular, and I am trying to find a way to wait until all of the data-ng-include elements have been evaluated and the includes finished loading. For instance the menu is a view that is loaded, as is the main content for each page, so at the very least there are two data-ng-includes that get evaluated and loaded. On top of that the menu include has nested data-ng-repeats that build out my menu. I need a way to initiate script AFTER all of these includes and any angular functions inside them have been loaded and the DOM is ACTUALLY ready.

Is there any event that gets fired when Angular has finished setting up the page?

The implementation of AnguarJS is basic and we are basically using it for templating via use of data-ng-include. Each page is a view (not an angular view, just an html file), loaded via data-ng-include, with the header and footer above and below it. The header also dynamically loads a global view (again not an angular view) html file that includes angular.

So far the menu is the only thing using AngularJS for anything other than templating. It uses it to create a menu using a JSON object that is dynamically generated by JSP and inserted into the DOM that is returned from the server.

In order to make life easier for the front-end guys, any kind of repeated JavaScript functionality is coded out into modules, which are detected on page load and loaded dynamically using a data-features attribute on the element the JavaScript functionality needs to be attached to.

For instance, you may have <div id="mySubMenu" data-features="subMenu"></div>. On page load, each element with a data-features element is detected, and the associated JS module loaded, in this case we would load /scripts/modules/subMenu.js.

What I need is a way to delay running this feature detection and attachment until all of the elements resulting from an include or other angular function are on the page and ready to be manipulated, especially since there may be elements with data-features attributes inside those includes.

Someone elsewhere had mentioned putting a controller on the body and putting nothing side of it but:

$scope.$on('$viewContentLoaded', function() {
    // Init Features Here
});

That did not work, so I am here looking for other options.

I ended up going with Theo's solution in this question: Sending event when angular.js finished loading

Community
  • 1
  • 1
StephenRios
  • 2,192
  • 4
  • 26
  • 42
  • The best answer is NO. You have to change the way you are looking at your app and Model and adapt it to the "angular way". Most likely there is a healthier way to achieve what you need – Dalorzo Feb 10 '14 at 20:26
  • 3
    And what exactly is the "angular way" of attaching traditional JavaScript functionality to page elements after they are loaded (which is basically what I am asking here)? – StephenRios Feb 10 '14 at 20:38
  • If you need to attach events to elements probably you may want to use a directive but of course I do not have enough information to judge at this stage what is best. – Dalorzo Feb 10 '14 at 20:41
  • Explanation updated and more detail added to aid you in answering the question. – StephenRios Feb 10 '14 at 20:52
  • You need a directive... Any attempt to go on the opposite direction will delay your from finding the right answer. – Dalorzo Feb 10 '14 at 21:17
  • The closest functionality for directives I can find was this fiddle (from AngularJS site): http://jsfiddle.net/api/post/library/pure/. The problem is I need to be able to keep my javascript modularized so the developing team under me can edit a specific file for each module, and I do not see a way to do this with these directives. – StephenRios Feb 10 '14 at 21:37
  • angular.module is one way and again, you answer is a directive – Dalorzo Feb 10 '14 at 21:41
  • Could you post an example of such an implementation as an answer so I can choose it as the accepted answer please? – StephenRios Feb 10 '14 at 21:51
  • @Dalorzo The problem is, if you don't have access to the code modules/directives that you want to detect are ready (i.e. some third party library) then you're screwed, unless you go and modify the library yourself, or post an issue and hope it gets fixed. Angular makes detecting when things are loaded difficult unless you own all your code. Now, imagine being able to [attach directives onto child elements before they are compiled](https://github.com/angular/angular.js/issues/6950). You'd be able to morph the behavior of third party libraries more easily, without touching their code bases. – trusktr Oct 17 '14 at 06:54
  • @trusktr I am not sure what you mean by "don't having access to directives" and you will have to give a sample of what you mean but it seems that angular clearly is able to handle what you need. – Dalorzo Oct 17 '14 at 15:19
  • @Dalorzo What I mean is that if you use 3rd party modules, for example, you might not want to modify those source codes, yet you need to tweak some behavior. To do this you want to patch the module (and by patch i mean by writing your own code that patches the behavior or the library, not a git or svn diff patch). There are plenty of modules on ng-modules.org that have nice *encapsulated* behavior, but often you find yourself stuck if the module doesn't do something you want it to. We don't want to modify dependency code in our own source code repository. – trusktr Oct 18 '14 at 08:52
  • So, that being said, if there was a way to attach directives on to children of our own directives (using css selectors for example), we'd have lots of power in our hands when dealing with code that we can't or shouldn't have to modify. – trusktr Oct 18 '14 at 08:55
  • @Dalorzo Of course, it's just that having a feature like what i suggested would make it extremely easy. Directives share controllers with all the other directives of an element, so we'd have direct access to the controller of the element we attach a ditevtive to. – trusktr Oct 18 '14 at 16:04

6 Answers6

15

on content loaded:

$rootScope.$on('$includeContentLoaded', function() {
    //do your will
});

on content requested:

$rootScope.$on('$includeContentRequested', function() {
    //...
});
easyrider
  • 701
  • 3
  • 10
  • 20
8

I am certain that this is not the correct way to do it but....

Putting the below function in my view controller, ran the function after the view had loaded in my application. I believe that the function is run in the next digest cycle following the view load (correct me here if wrong), so it is run once the page is formed.

setTimeout(function(){
  //do this after view has loaded :)
  console.log('success!');
}, 0);

Its possible you could chain these together via callbacks and/or use an async parallel library to execute another function, after each of the views setTimeouts had returned.

David
  • 3,166
  • 2
  • 30
  • 51
  • 1
    So I should really be using $timeout (the angular wrapper version) instead of setTimeout fyi – David Dec 17 '15 at 02:09
  • 2
    Not in this case. The problem here is registering a function BEFORE angular is ready but only running it AFTER angular has done it's initialization. So we can't use $timeout at this point. – GeekyMonkey Jun 27 '16 at 14:27
3

I think the appropiate answer is NO. You have to change the way you are looking at your app and Model and adapt it to the "angular way". Most likely there is a healthier way to achieve what you need. If you need to attach events to elements probably you may want to use a directive but of course I do not have enough information to judge at this stage what is best.

angular.module along with directives is a good option to explore.

Here is a plunker demo on how to use both with a JQUERY plug in:

http://plnkr.co/edit/WGdNEM?p=preview

Here is a an example with a directive on a separate file:

http://plnkr.co/edit/YNBSWPLeWqsfGvOTwsMB?p=preview

Dalorzo
  • 19,834
  • 7
  • 55
  • 102
  • This method still would require me to have a file with all of my directives in it, and load that file on every page load. I need a way to keep each directive in an individual file and load it dynamically as needed. – StephenRios Feb 10 '14 at 22:47
  • Each directive can be a separate file, that is actually how I do it during development. Here is an example: http://plnkr.co/edit/YNBSWPLeWqsfGvOTwsMB?p=preview – Dalorzo Feb 10 '14 at 22:51
  • Then the last piece of the puzzle is being able to load them dynamically so that they aren't all loaded on every page. – StephenRios Feb 11 '14 at 12:32
  • 2
    I'm sure this is the way to go forward when building legit Angular applications. I really just needed something to let me hybridize my application between traditional and Angular. Thank you so much for all your help. – StephenRios Feb 11 '14 at 14:43
3

You can use a JQuery Promise, and fulfill this promise within your angular root controller. Setup the promise before the angular controller or jquery code runs:

var AppReadyDeferred = $.Deferred();
var AppReadyPromise = AppReadyDeferred.promise();

Inside your angular controller once everything is ready, do

AppReadyDeferred.resolve();

And in your jQuery code wait for it to be resolved

window.AppReadyPromise.done(() => {
  // Angular is ready to go!;
});
GeekyMonkey
  • 12,478
  • 6
  • 33
  • 39
1

Use ng-cloak to delay templates being shown before compilation. You can also add a hidden field as the very last child to your html element that has ng-cloak (better to use it in body tag or wherever your ng-app starts) and then you can check for existence of this hidden element in an interval loop

Your hidden field part would be like this

<div ng-include="'ngtplhidden'"></hidden>
<script type="text/ng-template" id="ngtplhidden">
<span id="elemidToCheckInAnIntervalFunc"></span>
</script>
Nihat
  • 3,055
  • 3
  • 18
  • 28
0

You can try using document.onreadystatechange

document.onreadystatechange = x => {
    console.log('ready!');
}
artemnih
  • 4,136
  • 2
  • 18
  • 28