Angular directive: how to make sure all nested tem

2019-07-16 14:59发布

问题:

Suppose, I am making a custom Angular directive that has to examine and manipulate the DOM tree inside it (to be precise: the DOM tree under the element the directive is applied to). The right place for such manipulation is the directive's post-link function.

That works fine while all the HTML inside the directive is inlined. Problems appear when inside the directive we have other directives that load their templates using "templateUrl" property, or just "ng-include" directives to insert partials.

Those templates and partials are loaded asynchronously. That means, at the compile stage Angular will initiate the partial loading and will continue compiling without waiting for the loading to complete. Then, at the moment the parent directive is linked, the contained partials loading may still be in progress, so the directive's post-link function sees nothing inside.

In other words: the directive's post-link function is designed to have all nested DOM ready by the moment it is called, but with the async templates and includes this is not the case!

And template pre-loading does not help, because they are still accessed asynchronously.

How do people overcome that?

The task seems to be quite common, but I did not manage to find a good and reliable solution. Do I miss something obvious?...

Update: Well I have created a Plunk to illustrate the problem. Actually it reproduces only the problem with ng-include, the external template for sub-directive works. In my project it did not though, maybe this is a race condition, I have to investigate more.

回答1:

You can wait for the load of the main view with:

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

This trick, same as $timeout, not works if you are loading views with ng-include. You should wait for all the partial views. The right events order is:

  1. $viewContentLoaded
  2. $includeContentRequested
  3. $timeout
  4. $includeContentLoaded

You can use $includeContentRequested and $includeContentLoaded with a counter for wait the content of all included partials:

var nIncludes = 0;
$scope.$on('$includeContentRequested', function (event, templateName) {
  nIncludes++;
  console.log(nIncludes, '$includeContentRequested', templateName);
});

$scope.$on("$includeContentLoaded", function (event, templateName) {          
   nIncludes--;
   console.log(nIncludes, '$includeContentLoaded', templateName);
   if (nIncludes === 0) {
     console.log('everything is loaded!!');
     // Do stuff here
   }            
}); 

I have not found another more elegant solution.



回答2:

use $timeout for the external template directive (This will still not work for ng-include). it will call when async loaded template finish loading. (By the way this is clean code, it won't cause performance issue)