Now we all know AngularJS is an awesome framework to build large Single Page Applications. At the core of angular is a little thing called the HTML Compiler. It takes text as input (most likely from your templates) and transforms it into DOM elements. In the process it also does a bunch of things like:
Let me drive your attention to one area: Template Expansion. I was curious to know if AngularJS could be used purely as a template expander. Something akin to Handlebars or Underscore templates. Is it possible? Can we take that aspect of Angular and use it independently?
Well, it turns out, it’s not that straightforward. But hey, this is an experiment and we are on a path to discover something!
A valid question. YMMV: Your Motivations May Vary. For me it was about:
Disclaimer
Now, if you were to ask me point-blank: “Should I be doing this in my project?”, I will say “NO”, without blinking an eyelid.
Templates are the fundamental building blocks of modern-day web apps. It enables better structuring by keeping your view separate from the domain logic. It also gives you composability where you can compose the UI by combining a set of templates. This is of course better than one giant, monolithic HTML file. So there are real, practical benefits to using templates.
Angular definitely gives you this capability but does so with a few key abstractions:
Let’s see them in turn.
$http
request to fetch the template from a URL. Before it does that, it checks the $templateCache
if it’s already available. If not, it goes out to get it.scope
will generate the jqLite element (DOM)scope
from the previous step must have $digest
invoked to do the real “template expansion”. The $digest
is the necessary syncing mechanism between the scope and the DOM.At the end of step 5, we should have a jqLite element
bound to the right data from the scope
. Now, the reason why we are doing this is to generate a template-expanded text. So we really need the text part.
This can be done by reading the element
’s outerHTML
property, like so: element[0].outerHTML
. Finally, we have what we started out to get. It was a little round-about but we used a template and expanded it to real text by supplying a scope
(the context for the template). All with AngularJS.
I must admit, the above is not the complete picture to generate template-expanded text. In order to use the services such as $templateRequest
, $templateCache
and $compile
, you have to rely on Angular’s $injector
. Additonally the scope has to be an instance of the angular Scope
. It can’t be a plain JavaScript object! To create a scope you have to rely on $rootScope
, which you also get from $injector
.
[Aside]: If you try using a simple object, you will see exceptions being thrown. Also we need to call $digest()
to bind the data. As you guessed, we can’t do that on a simple js-object.
The code below is a working example of using Angular to expand a template. You can copy and paste it as a snippet in Chrome DevTools, and execute to see the results. Make sure you run on a web-page that uses the AngularJS library. AngularJS.org is a decent bet.
// The injector knows about all the angular services
var injector = angular.element(document).injector();
// This template could have been fetched with a $templateRequest
// We are inlining it here for this snippet
var template = '<div>{{ ::firstName }} -- {{ ::lastName }}</div>';
// $compile creates a template-function that can be invoked with the scope
// to expand the template
var templateFunction = injector.get('$compile')(template);
// Create the scope.
// Note: this has to be a real angular Scope and not a plain js-object
var scope = injector.get('$rootScope').$new();
// Set some properties
scope.firstName = 'Pavan';
scope.lastName = 'Podila';
var element = templateFunction(scope);
scope.$digest();
// Remove the noise around the generated element. This can be disabled
// by configuring the $compileProvider.debugInfoEnabled()
// Here we take the easy way out
element.removeClass('ng-scope ng-binding');
// -------- GRAND FINALE ---------
// The expanded template as TEXT
// -------------------------------
var expandedText = element[0].outerHTML;
// Output:
// <div class="">Pavan -- Podila</div>
As you can see, it is quite round about. Definitely not suggested for a real project. If you need such capability, you are better off with Handlebars or Underscore templates. But if you have read this far, hopefully you have put an additional fold in your Angular grey matter!