The other day, when I was exploring routing, nested views, and caching with ngRoute in AngularJS 1.x, I noticed something very interesting: the network latency, that I was simulating with an $http interceptor, was also delaying the loading and rendering of ng-template content. After a little digging into the AngularJS source code, I discovered that ng-template requests go through the $http service just like any other URL-based request; the only difference is that the requests are accompanied by the $templateCache.
The beauty of the Script / ng-template directive is that content can be inlined and made available immediately, without having to go across the wire to your server. Beyond the philosophy of it, however, I never really thought much about how ng-template was implemented. But, once I saw that $http interceptors were affecting ng-template load times, I started to follow the request down the rabbit hole.
Staring with the ngInclude directive, ngInclude uses the $templateRequest() service to request the template. The $templatRequest() service then turns around and calls the $http() service; but, as part of the HTTP configuration object, it passes-in the $templateCache service. The $http() service then initiates the request, which is implemented as an asynchronous promise chain. The request workflow then looks in the provided cache, finds the cached template, and resolves the request before it has to go over the wire (ie, actually make an HTTP network call). The ngInclude directive then $compile()'s the resolved template and transcludes it into the DOM (Document Object Model).
It's just that simple (ha ha). Basically, it boils down to:
ngInclude -> $templateRequest -> $http.get( w/ cache:$templateCache ) -> $compile()
NOTE: The generic Directive "templateUrl" is also retrieved using the $templateRequest() and will, therefore, also be affected by $http interceptors.
To demonstrate this workflow, I've created a small app that has three ngInclude directives that leverage ng-template content. I also have an $http interceptor that incrementally slows down the request / response lifecycle. As such, we'll see the ngInclude directives slowly cascade onto the page:
As you can see, each $http request is incrementally slowed down by 1,500 milliseconds. And, when we run the above code, we get the following page output:
As you can see, each of the ngInclude directives was rendered 1.5 seconds after the previous one. This is because each of them was delayed an additional 1,500 milliseconds in the $http service.
There's probably no practical take-away from this exploration. More than anything, it was just interesting to follow the workflow under the hood and see how everything ties together. It's quite fascinating to see how well all of the core AngularJS services work together in order to achieve awesomeness.
Want to use code from this post? Check out the license.
Ben, as always, thanks for finding out these hidden angular tips and gotchas. Just wondering, is there a reason that you don't use build time tools like grunt-ngtemplate or gulp-ngtemplate to add the partial views $templateCache? We use this method to reduce the number of http request when loading / navigating through the app. What are your opinions on this?
I was surprised when I learned about this too! There are some interesting things you can do with it, though. For instance, check out this Gist:
I thought that was a pretty neat example of how you might use $http interceptors for templates.
Good question; I actually do use a "build step" of sorts. But, it's kind of janky. Right now, I actually use ColdFusion to include all of the templates into the main page request:
That said, I am starting to try to learn more about proper build tools like Gulp.js to handle more of this more me. I just haven't started a new project in a while that really necessitated a change in workflow.
Very interesting. I'm having a little trouble with the context, like where does the `activeProfile` come from. Is there a presentation that goes along with it? It looks like it was related to ng-conf.
Is there a way to overcome a directive's dependency on the http latency? Case in point: A have a directive for a Submit button that will disable the button whenever there is an outstanding http response. The directive uses a template to make the Submit button. When I simulate a delay, the Submit button does not show on the page until after the delay, during the first time the page loads. I would like for the Submit button to show immediately when the page loads, not after the http delay.
FYI: the Submit button initiates a request for more info which loads in a view, so the page is a SPA. Thus when the Submit button is clicked it auto-disables while it waits for the response, then re-enables once the response is received. It has an ng-click action that initiates the request, but the auto-disabling is hidden in the directive definition function that uses an http interceptor.
Thanks for your awesome info!!!
I just realized that the behavior I see in the Submit button directive has been there all along, even before I decided to put in the auto-disabling. So I think my issue is with the nature of the beast and cannot be overcome.