ngTemplate 1.0
ngTemplate
is a light-weight DOM-based template engine, inspired by AngularJS.
mustache.js
, Handlebars
or _.template
are all nice and shiny, until it comes to a form.
With every rending these template engines replace the bound DOM subtree
and the state of inputs gets lost.
ngTemplate
treats the DOM carefully. It modifies the exact target nodes gracefully
according to the directives and actual state.
Motivation
- Progressive enhancement friendly: Server-side generated HTML can be fully ready for presentation. During
ngTemplate
synchronization it will be updated according to element directives and a provided state - HTML compliant:
data-*
- directives instead of foreign concepts such as{{foo}}
,[hidden]
,*ngFor
,[(ngModel)]
- Concern separation: Presentation state logic decoupled from the view
- Performance:
ngTemplate
modifies DOM nodes by state diff; it touches the DOM only when it's necessary - Easy to catch up: Familiar for Angular folks directives such as
data-ng-if
,data-ng-switch
,data-ng-for
and a few extra intuitive e.g.data-ng-text
,data-ng-class
- Really small library: minimized gziped size is 4K
- Definitely Typed: IDE checks on the fly if you violate any of declared interfaces (if not the compiler does)
How does it work?
Templates are written with HTML that contains ngTemplate
-specific data attributes (data-ng-*
):
Name Name is required Hero Power Nothing here Power is required Submit
ngTemplate
synchronizes the template with the passed state. Let's say we have the template HTML within a DOM element,
so we initialize the template like let template = new NgTemplate( el );
. Alternatively we can populate
the bounding element with the template from the passed string: let template = new NgTemplate( el, '<i data-ng-if="foo">Hello!</i>' );
As soon as we have template
object we can sync it to a specified scope. E.g. template.sync({ foo: true });
makes
variable foo
available for template expressions, template.sync({ foo: { bar: true }});
gets accessable as foo.bar
; let el = document elName = document elPower = document; // Bind the templatelet template = el ; // Set the scope (view state)let scope = powers: "-" "Really Smart" "Super Flexible" "Super Hot" "Weather Changer" power: valid: true name: valid: true form: valid: false ;// Sync to the scopetemplate;
Live Demo
Installing
You can get ngTemplate
via npm.
npm i --save ng-template
Accessing module
TypeScript
;const template = node ;
CommonJS
var NgTemplate = NgTemplate template = node ;
In order to adapt your source for browser, you can either browserfy or load your modules with SystemJS
System.config({
paths: {
"*": "node_modules/*"
},
packageConfigPaths: [ "./node_modules/*/package.json" ]
});
VanillaJS
<script src=""./node_modules/ng-template/dist/ngtemplate.glob.min.js"></script>
var template = node ;
API
;let template = el tpl ; // compile templatetemplate; // render on the first run and after synchronize it with the scope// optionalconsole; // find the synchronization details
where:
- el - a DOM element we bind to
- tpl - OPTIONAL: template code that will be injected into
el
- scope - an object literal (template scope) whose members form the scope for template expressions
Also can be instantiated via the factory:
NgTemplate ;
Options
You can define though the constructor options the callbacks willMount
and didMount
. The first one gets invoked straight before the ngTemplate
populates
bounding element's inner HTML from the template string. ngTemplate
calls the second callback after that:
;let template = el tpl { console; } { console; };
Template expressions
Template expressions are being evaluated in the given scope
. So we can reference scope variables within template e.g.
data-ng-if="foo"
refers to foo
variable of the scope and therefore:
template; // show elementtemplate; // hide element
We access scope objects the same way we do it in JavaScript e.g. data-ng-if="foo.bar"
refers to foo.bar
and can be toggled like this:
template; // show element
Expressions may have mixed scope variables and primitives:
data-ng-if="foo && bar.baz || false"
data-ng-if="foo + 10 > 20"
We can pass rendering helpers (e.g. transformers) with the scope. For example we pass decorator
to the directive data-ng-if="decorator(foo)"
this way:
{
foo: "foo",
decorator: function( val ){
return "decorated " + val;
}
}
Expressions are evaluated in the context of the target element, so we can access the element with this
:
data-ng-if="foo && this.checked"
❗️ NOTE: In order to gain better performance keep to primitive expressions especially in cyclic directives e.g.
data-ng-text="foo.bar.baz"
,data-ng-text="!foo.bar.baz"
,data-ng-text="'string here'"
,data-ng-if="foo.bar > baz.quiz"
,data-ng-text="foo + 10
,data-ng-if="true"
,data-ng-prop="'disabled', true || false"
,data-ng-data="foo || bar, baz"
. Such expressions are being evaluated without use ofeval()
and therefore the process takes much less time and resources. You can check how the parser treats your expressions by studying content oftemplate.report().tokens
array
ngTemplate Report
You can get template synchronization details like that:
let tpl = document "<span data-ng-text=\"foo.bar.baz\"></span>"; tpl;console; // [ "'foo.bar.baz' is undefined" ]
Directives
ngText
We use ngText
to modify element's textNode
Syntax
<el data-ng-text="expression => text:string|number" />
Examples
documentbody `<i data-ng-text="foo"></i>` ; console; // <i>Foo</i>
HTML gets automatically escaped:
documentbody `<i data-ng-text="foo"></i>` ; console; // <i><button></i>
ngProp
We use ngProp
to modify element's properties
Syntax
<el data-ng-prop="expression => propertyName:string, expression => value:boolean|string" />
// Can be applied multiple times on element
<el data-ng-prop-0="..." data-ng-prop-9="..." />
Examples
documentbody `<button data-ng-prop="'disabled', isDisabled"></button>` ; console; // <button disabled=""></button>
ngAttr
We use ngAttr
to modify element's attributes
Syntax
<el data-ng-attr="expression => attrName:string, expression => value:boolean|string" />
// Can be applied multiple times on element
<el data-ng-attr-0="..." data-ng-attr-9="..." />
Examples
documentbody `<input data-ng-prop="'required', required">` ; console; // <input required="true">
ngData
We use ngData
to modify element's dataset
Syntax
<el data-ng-data="expression => datasetKey:string, expression => datasetValue:string" />
// Can be applied multiple times on element
<el data-ng-data-0="..." data-ng-data-9="..." />
Examples
documentbody `<div data-ng-data="'dateOfBirth', value"></div>` ; console; // <div data-date-of-birth="1960-10-03"></div>
ngClass
We use ngClass
to modify element's classList
Syntax
<el data-ng-class="expression => className:string, expression => toggle:boolean" />
// Can be applied multiple times on element
<el data-ng-class-0="..." data-ng-class-9="..." />
Examples
documentbody `<i data-ng-class="'is-hidden', isHidden"></i>` ; console; // <i class="is-hidden"></i>
or
documentbody `<i data-ng-class="className, isHidden"></i>` ; console; // <i class="is-hidden"></i>
ngIf
We use ngFor
to toggle visibility of an element (subtree) within the DOM
Syntax
<el data-ng-if="expression => condition:boolean" />
Examples
let template = documentbody `<i data-ng-if="toggle">Hello!</i>` ; template;console; // <ng style="display: none; "></ng> template;console; // <i>Hello!</i>
ngFor
We use ngFor
when we need to generate a list of elements (subtrees)
ngFor
treats the generated list gracefully. It tries to maintain the nodes once appended to the DOM on list data change. However of removing rows from the list during synchronizationngFor
can do it only if a unique rowid
property provided. Otherwise it updates the list
Syntax
<el data-ng-for="let variable of expression => list:any[]" />
Examples
documentbody `<i data-ng-for="let row of rows" data-ng-text="row"></i>` ; console; // <i>foo</i><i>bar</i>
ngSwitch
We use ngSwitch
when we need to display on element (subtree) of a set of available options.
Syntax
<el data-ng-switch="expression => variable:string|number">
<el data-ng-switch-case="expression => value:string|number" />
<el data-ng-switch-default />
</el>
Examples
documentbody `<div data-ng-switch="theCase"> <i data-ng-switch-case="1">FOO</i> <i data-ng-switch-case="2">BAR</i> </div>` ; console; // <i>FOO</i>
documentbody `<div data-ng-switch="theCase"> <i data-ng-switch-case="1">FOO</i> <i data-ng-switch-case="2">BAR</i> <i data-ng-switch-case-default>BAZ</i> </div>` ; console; // <i>BAZ</i>
ngEl
We use ngEl
to modify element properties
❗️ NOTE: Using
ngEl
is rather discouraging as it cannot be cached and every model sync will cause the DOM modification even if the expression ofngEl
wasn't changed
Syntax
<el data-ng-el="expression => eval:void" />
Examples
documentbody `<i data-ng-el="this.className = class"></i>` ; console; // <i class="is-hidden"></i>
<i data-ng-el="this.textNode = mymodel.foo"></i><i data-ng-el="this.setAttribute( 'name', mymodel.foo )"></i><i data-ng-el="this.disabled = state.isVisible"></i><i data-ng-el="this.classList.toggle('name', model.foo)"></i>
Contributing
ngTemplate
welcomes maintainers. There is plenty of work to do. No big commitment required,
if all you do is review a single Pull Request, you are a maintainer.
How to build
This source code is written in TypeScript. In order to build the app simply run tsc
in the root directory
How to test
There two options. You can run unit-tests in the console:
npm run test
or you can fire up tests/index.html
in a browser