Preloading Data Before Executing ngInclude In AngularJS
Earlier this week, I looked at loading AngularJS components with RequireJS after your application had been bootstrapped. While interesting, I disliked the fact that my calling code needed to know about the lazy-loading behavior. What I really wanted to do was centralize the lazy-loading around the part of the app that actually required it. And, since I use ngSwitch, ngSwitchWhen, and ngInclude to manage my nested views, what I really wanted to do was defer lazy-loading to the use of the relevant ngInclude directives.
While this is more of an exploration in understanding how compilation and transclusion works in AngularJS, I envisioned a ngSwitchWhen directive that looked something like this:
<div ng-switch-when="module" bn-preload="module-data" ng-include=" 'module.htm' "> </div>
In this case, the "bnPreload" directive would lazy-load AngularJS components before allowing the ngInclude directive to make the subsequent HTTP request and update the DOM. However, it would only do this when the ngSwitchWhen directive matched. So, the workflow would be something like this:
- ngSwitch watches expression and transcludes appropriate ngSwitchWhen element.
- bnPreload stops the processing.
- bnPreload lazy-loads module.
- bnPreload transcludes the ngInclude element.
- ngInclude makes HTTP request to populate template cache.
- ngInclude compiles and appends content.
I didn't want to mess with the actual ngInclude code - I wanted the bnPreload directive to use the normal directive compilation and linking rules. Now, when it comes to compiling and transcluding in AngularJS, I'm still a total novice; however, after about an hour of tinkering I finally figured something out.
In the following demo, I have two ngSwitchWhen cases that both lazy-load data before allowing the relevant ngInclude directives to proceed. In order to simulate the lazy-loading, I'm using $timeout() and promises. The trick was to allow both the ngSwitchWhen and the bnPreload directives to compile and transclude the same element. The complication laid in the fact that bnPreload had to clean up after itself.
At this point, I honestly don't want to say too much more since I am sure that I will do more to mislead than to inform. Simple linking in AngularJS, I understand. It makes perfect sense to me. Transcluding and linking, on the other hand, is still very new and confusing. If I try to explain how it works, I'll probably just end up confusing myself.
Want to use code from this post? Check out the license.
Ben, what does your "bn-include-log" directive do?
Not really sure I understand what the benefit of lazy loading is. What is the end game with require js?
the bnIncludeLog was simply to see *when* directives at a lower priority (lower that bnPreload) would be linked. Since bnIncludeLog and ngInclude both have a priority of zero, I was assuming that it would, therefor, indicate when the ngInclude was executed.
Basically, I wanted to make sure that ngInclude *execution* was actually being delayed not just the rendering (ie, it was being linked, but not shown until it was transcluded).
Sorry, that's confusing, I know -- I'm still wrapping my head around it.
The end-game here is to be able to defer the loading of portions of an AngularJS application until they are actually being used. Right now, I load ALLLL my app at the start (JS + Views). But, the app is getting fairly large. What I'd like to do is defer loading of not-commonly-used portions until they are actually used by the user.
Right now, I'm just exploring and trying to understand how all this stuff works.
@Ben, that makes sense. We have just started a full blown angular app and I expect that I will be traveling down this road myself soon enough.
For my last few blog posts, I've been building off the concept laid out here:
But, Ify rocks his preload in the Routing mechanism. I have a somewhat different approach to my view rendering; so, I wanted to see if I could move the preloading closer to the HTML that requires it.
Good luck with your App - I've been *loving* AngularJS. And, apparently v1.2 just came out today!
Not sure/understanding why you want to go this direction. The HMTL is now controling which data is loaded. I still try to see HTML as presentation without (to much) logic, what to load. That's all inside js. Preloading sounds to me like against all the whole async idea?
If you put things inside lazy controllers, the controller is always fired first, the 2way binding scope magic will do its job if needed.
So the magic is the html controller call thatnmakes any controller prebind/load the JS.
As always thank for your greatcontributions, insides and doubts.
You raise a good question - if the only thing I were loading was "data," then yes, I would simply load the controller and then load the data asynchronously inside the controller (if that is what you're saying). In fact, most of my controllers actually do work in this fashion:
2. Show "loading" UI.
3. Load "remote data" asynchronously.
4. Render data (once loaded).
However, the root desire here isn't just to load data - that's just the proof-of-concept; the real desire here is load actual *Modules* "on the fly" when pulling up parts of a UI.
In my previous post:
... I talk about lazy-loading modules. However, in that post, I needed the calling controller to know all about the lazy-loading.
What I'd really like is to decouple the lazy-loading from the controller and bring it closer to the part of the app that actually needs it. And, since most of my UI is built with ngSwitch/ngInclude statements, I think I could insert the login into the ngInclude "preload"... which is where this current post comes into play.
So, instead of simply preloading "data", imagine that the "preload" is for the UI/Controllers that are about to be consumed.
You could simply use a variable for the src attribute that will be published after the data is loaded and a ng-hide to not show the container while loading the html, which will be switched by the onload attribute or the event that is broadcasted by ng-include itself.
So you would save a lot of code for this effect.
I've been thinking a lot about your suggestion. I think it does make sense. However, I am not sure how much code it would end up saving. To get the same kind of functionality, I would still need:
1. To $watch the switch expression for changes so that I could preload the right data (based on the switch evaluation).
2. Have a directive on the switch case so that I could update the DOM with the "Preloading..." message while the data loads.
3. To have the preload mechanism (ie, the thing that actually does the data loading).
That said, I think I could probably put the "Preloading..." message inside the Div that will get swapped out with the ngInclude since ngInclude should replace the content of the Div. So, I think I could get away with needing a directive to show the message. But, I would still need something to do handle the loading, which means that the logic either needs to go into the parent Controller; or, I would need to create a Directive/Controller on the switch case.
so... how do i use bn-preload, with angular1.2?
i love the idea of prereqs before an ng-include fires... but the compound transclusion thing is killing me!
find a workaround?
you mentioned the thing in your compound transclusion article, but the only suggestion was to wrap a <div ng-include> inside the ng-switch... but didnt mention how the bn-preload could work.