Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at the New York ColdFusion User Group (Dec. 2008) with: Michael Dinowitz
Ben Nadel at the New York ColdFusion User Group (Dec. 2008) with: Michael Dinowitz@mdinowitz )

The Flexible Syntax Of Structural Directive Expressions In Angular 2 Beta 14

By Ben Nadel on

Yesterday, I started digging into structural directives in Angular 2. As I was building my index-loop directive, I noticed that the use of the ";" delimiter in my directive expression seemed to be optional. After a little more trial and error (and perusal of the source code), I realized that most of the operators and separators in the directive expression are optional and are there only for enhanced readability and developer preference.

Run this demo in my JavaScript Demos project on GitHub.

When it comes to structural directive expressions (using the "*" syntax sugar) there are two sets of bindings that we are configuring:

  • Input property bindings.
  • View-local variable bindings.

There seem to be a few hard-and-fast rules regarding these bindings:

  • When it comes to the input property bindings, we cannot use the "=" assignment operator.
  • When it comes to the view-local variable bindings, we have to use the "=" assignment operator (except for the implicit binding).
  • The implicit view-local variable binding (ie, #i) has to be the first sub-expression.

Other than that, the use of the ":" operator and the use of separators (such as ";" and ",") are completely up to you.

To demonstrate, I'm going to take the bnLoop index-loop structural directive from yesterday and remove just about everything but a console.log() statement. Then, we'll create five instances of the directive that are functionally equivalent but have completely different syntax:

  • <!doctype html>
  • <html>
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • The Flexible Syntax Of Structural Directive Expressions In Angular 2 Beta 14
  • </title>
  •  
  • <link rel="stylesheet" type="text/css" href="./demo.css"></lin>
  • </head>
  • <body>
  •  
  • <h1>
  • The Flexible Syntax Of Structural Directive Expressions In Angular 2 Beta 14
  • </h1>
  •  
  • <my-app>
  • Loading...
  • </my-app>
  •  
  • <!-- Load demo scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/14/es6-shim.min.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/14/Rx.umd.min.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/14/angular2-polyfills.min.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/14/angular2-all.umd.js"></script>
  • <!-- AlmondJS - minimal implementation of RequireJS. -->
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/14/almond.js"></script>
  • <script type="text/javascript">
  •  
  • // Defer bootstrapping until all of the components have been declared.
  • requirejs(
  • [ /* Using require() for better readability. */ ],
  • function run() {
  •  
  • ng.platform.browser.bootstrap( require( "App" ) );
  •  
  • }
  • );
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // I provide the root application component.
  • define(
  • "App",
  • function registerApp() {
  •  
  • // Configure the App component definition.
  • ng.core
  • .Component({
  • selector: "my-app",
  • directives: [ require( "Test" ) ],
  •  
  • // The "*" syntactic sugar for structural directives in Angular 2
  • // affords a lot of optional syntax for enhanced readability (and
  • // personal preference). All five of the following *bnTest
  • // expressions are functionally equivalent. Notice the swappable
  • // and optional use of " " and ";" and "," as statement delimiters
  • // and the optional use of ":" as the assignment operator.
  • // --
  • // NOTE: View-local variables (ex, #first) are different than
  • // input bindings and have to use the "=" assignment operator.
  • // And, when expressions have an implicit view-local variable
  • // (such as #i in this case), it always has to go first.
  • template:
  • `
  • <section *bnTest="#i from 1 to 11 step 2 #first = first #last = last">
  • {{ i }} - {{ first }} - {{ last }}
  • </section>
  •  
  • <section *bnTest="#i from:2 to:12 step:2 #first=first #last=last">
  • {{ i }} - {{ first }} - {{ last }}
  • </section>
  •  
  • <section *bnTest="#i from 3 ; to 13 ; step 2 ; #first = first ; #last = last ;">
  • {{ i }} - {{ first }} - {{ last }}
  • </section>
  •  
  • <section *bnTest="#i ; from:4 ; to:14 ; step:2 ; #first = first ; #last = last ;">
  • {{ i }} - {{ first }} - {{ last }}
  • </section>
  •  
  • <section *bnTest="#i , from:5 , to:15 , step:2 , #first = first , #last = last">
  • {{ i }} - {{ first }} - {{ last }}
  • </section>
  • `
  • })
  • .Class({
  • constructor: AppController
  • })
  • ;
  •  
  • return( AppController );
  •  
  •  
  • // I control the App component.
  • function AppController() {
  •  
  • // Nothing to do here...
  •  
  • }
  •  
  • }
  • );
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // I provide a test structure directive that logs out its input bindings just
  • // to demonstrate that they were bound properly.
  • define(
  • "Test",
  • function registerTest() {
  •  
  • ng.core
  • .Directive({
  • selector: "[bnTest]",
  •  
  • // Because we are using the "*" template syntax, our expression
  • // is parsed into a number of individual inputs that are all
  • // prefixed with the directive name. So, given the expression:
  • // --
  • // #i from 1 to 10 step 1
  • // --
  • // ... we are given the following inputs:
  • // --
  • // bnTestFrom ( parsed "from" sub-attribute )
  • // bnTestTo ( parsed "to" sub-attribute )
  • // bnTestStep ( parsed "step" sub-attribute )
  • // --
  • // NOTE: The "#i" portion is a view-local variable that we have
  • // to setup on each instance of the template that we clone.
  • inputs: [ "from: bnTestFrom", "to: bnTestTo", "step: bnTestStep" ]
  • })
  • .Class({
  • constructor: LoopController,
  •  
  • // Define the
  • ngOnChanges: function noop() {}
  • })
  • ;
  •  
  • LoopController.parameters = [
  • new ng.core.Inject( ng.core.ViewContainerRef ),
  • new ng.core.Inject( ng.core.TemplateRef )
  • ];
  •  
  • return( LoopController );
  •  
  •  
  • // I control the Loop directive.
  • function LoopController( viewContainerRef, templateRef ) {
  •  
  • var vm = this;
  •  
  • // Expose the public methods.
  • vm.ngOnChanges = ngOnChanges;
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I get called whenever any one of the bound input values change.
  • function ngOnChanges( changes ) {
  •  
  • var viewRef = viewContainerRef.createEmbeddedView( templateRef );
  •  
  • console.log(
  • "ngOnChanges( from: %s, to: %s, step: %s )",
  • vm.from,
  • vm.to,
  • vm.step
  • );
  •  
  • // Set up all the local variable bindings.
  • viewRef.setLocal( "$implicit", vm.from );
  • viewRef.setLocal( "first", true );
  • viewRef.setLocal( "last", false );
  •  
  • }
  •  
  • }
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, we are implementing the *bnLoop structural directive with the following five expressions:

  • #i from 1 to 11 step 2 #first = first #last = last
  • #i from:2 to:12 step:2 #first=first #last=last
  • #i from 3 ; to 13 ; step 2 ; #first = first ; #last = last ;
  • #i ; from:4 ; to:14 ; step:2 ; #first = first ; #last = last ;
  • #i , from:5 , to:15 , step:2 , #first = first , #last = last

And, when we run the above code, we get the following output:


 
 
 

 
 Structural directive syntax is very flexible in Angular 2 Beta 14. 
 
 
 

As you can see, they all work perfectly well. Functionally, they are all equivalent. Just about all of the separators and input assignment operators are optional in Angular 2 structural directive expressions. It's up to you, as the developer, to choose a combination of tokens that lends well to readability and maintainability. With great power comes great responsibility.




Reader Comments

Is this documented somewhere. The ngFor does not use # as the start of the expression, whereas i can only get the *-magic working with a #-prefix

tx

Reply to this Comment

Just realised `#` has been replaced by `let`. So it seems this *-magic only works when using `#` or `let`. I cannot find such a statement however in the documentation.

Reply to this Comment

@Raphael,

Correct, the syntax was changing in one of the betas I think. Now, I believe that "#" / "var-" is only uses for Element/Component references and "let" is used for local template variables. So, I believe the syntax would now look like:

let i from 1 to 11 step 2 let first = first let last = last
let i from:2 to:12 step:2 let first=first let last=last
let i from 3 ; to 13 ; step 2 ; let first = first ; let last = last ;
let i ; from:4 ; to:14 ; step:2 ; let first = first ; let last = last ;
let i , from:5 , to:15 , step:2 , let first = first , let last = last

Its hard when the keep changing stuff, these posts, unfortunately, get out-of-date too quickly :(

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.