Angular Directive Grammar & Microsyntax: Demystifying the Hidden Parts

khangtrannn

Khang Tran ⚑️

Posted on November 3, 2023

Angular Directive Grammar & Microsyntax: Demystifying the Hidden Parts

Introduction

<div *ngFor="let leaf of ['πŸ€', 'πŸ€πŸ€', 'πŸ€πŸ€πŸ€']; let i = index;">
  <p>{{i + 1}}. {{leaf}}</p>
</div>
Enter fullscreen mode Exit fullscreen mode
1. πŸ€
2. πŸ€πŸ€
3. πŸ€πŸ€πŸ€
Enter fullscreen mode Exit fullscreen mode

If you've worked with Angular, I'm sure you've come across this *ngFor syntax at least once: let leaf of ['πŸ€', 'πŸ€πŸ€', 'πŸ€πŸ€πŸ€']; let i = index;". However, have you ever wondered?

  1. Is there any alternative *ngFor syntax?
  2. Where does the index variable come from?
  3. How does the leaf variable iterate over and reference items of ['πŸ€', 'πŸ€πŸ€', 'πŸ€πŸ€πŸ€']?
  4. Why is it let ... of ...? Can we change it to let ... something ...?

What do you think about trying the following syntaxes with *ngFor?

1. let leaf of ['🌱', 'πŸ€', '🌿']; let i = index;
2. let leaf; let i = index; of ['🌱', 'πŸ€', '🌿'];
3. let i = index; let leaf; of ['🌱', 'πŸ€', '🌿'];
4. let i = index let leaf; of: ['🌱', 'πŸ€', '🌿'];
5. let i = index; of ['🌱', 'πŸ€', '🌿']; let leaf;
6. let i = index; of: ['🌱', 'πŸ€', '🌿'] let leaf;
7. 'Magic πŸͺ„'; let i = index; of: ['🌱', 'πŸ€', '🌿']; let leaf;
8. let leaf = $implicit of ['🌱', 'πŸ€', '🌿']; let i = index;
9. let leaf = $implicit; let i = index; of ['🌱', 'πŸ€', '🌿'];
10. let leaf = $implicit, let i = index, of ['🌱', 'πŸ€', '🌿'];
Enter fullscreen mode Exit fullscreen mode

You can quickly find the answer via this StackBlitz link.
Surprisingly, these peculiar syntaxes compile without any errors. πŸ€”
How does this happen? What are the rules behind these syntax variations?


Demystifying *ngFor syntax

At first glance, you might be under the impression that you're required to use a semicolon to delimit the calls and adhere to a certain order, or that there might be more rules you don't yet understand about how to use the syntax. But that's not the case - the syntax is actually quite flexible more than that.

Angular's microsyntax has 4 building blocks, that when combined in a particular way, make up the entire microsyntax API. These building blocks are:

  • Expressions
  • The as keyword
  • Keyed expressions
  • let bindings

Angular microsyntax building blocks

1. Expressions

Anything that, when referenced, returns a value.

  • Raw value
<p *hello="'πŸ‘‹ Hey there'"></p>
Enter fullscreen mode Exit fullscreen mode
  • Calling a function
<p *hello="greeting()"></p>
Enter fullscreen mode Exit fullscreen mode
  • Referenced a variable
<p *ngIf="shouldDisplay"></p>
Enter fullscreen mode Exit fullscreen mode
  • Operator (9 * 9)

2. The as keyword

The rules behind the as keyword as an alternative to let. The most commonly used is combining between *ngIf and AsyncPipe.

<div *ngIf="(myFutureGirl$ | async) as myFutureGirl">
  {{myFutureGirl}}
<div>
Enter fullscreen mode Exit fullscreen mode
Oops, 404 πŸ€¦β€β™‚οΈ
Enter fullscreen mode Exit fullscreen mode

3. keyExp - Key Expressions

A key expression is simply an expression that you're able to bind to an input on a structural directive.

For example, the *ngFor directive includes an ngForOf input as follows:

/**
 * The value of the iterable expression, which can be used as a
 * [template input variable](guide/structural-directives#shorthand).
 */
@Input()
set ngForOf(ngForOf: U&NgIterable<T>|undefined|null) {
  this._ngForOf = ngForOf;
  this._ngForOfDirty = true;
}
Enter fullscreen mode Exit fullscreen mode

In this case, the key expression is the of keyword. This directly answers the question, Why is it let ... of ...?

4. let bindings

The let binding is used for reference the Template Context. Let's take a look at our example:

let leaf of ['🌱', 'πŸ€', '🌿']; let i = index;
Enter fullscreen mode Exit fullscreen mode

There are two types of references made using let bindings:

  • Implicit

In this case, the assigned expression on the right side is omitted, and it is equivalent to the following:

let leaf = $implicit
Enter fullscreen mode Exit fullscreen mode

Angular automatically assigns the reference with the $implicit key in the context.

  • Explicit
let i = index
Enter fullscreen mode Exit fullscreen mode

When we use let i = index, it is clear that the variable i references the index key in the context.


Combining Things Together

A chart taking a microsyntax and turning it into a diagram. This diagram will be explained thoroughly via text in this section

  • It starts with the * reserved token. It is an Angular special syntax companion with the Angular Structural Directive.
  • Then, you have to declare the selector value of the directive itself.
  • Finally, you can bind to the selector as with any other input using the = token.

The contents of the input itself are where the microsyntax goes.

1. First Item

Either an expression or a let binding.

If an expression is passed, the value of the expression will be passed to the same input name as the selector itself.

Let's take a look at *ngIf implementation:

@Directive({
  selector: '[ngIf]',
  standalone: true,
})
export class NgIf<T = unknown> {
  /**
   * The Boolean expression to evaluate as the condition for showing a template.
   */
  @Input()
  set ngIf(condition: T) {
    this._context.$implicit = this._context.ngIf = condition;
    this._updateView();
  }
}
Enter fullscreen mode Exit fullscreen mode

*ngIf includes an ngIf input that is the same as the [ngIf] selector itself.

If a let binding is the first item, it will work exactly as it's explained in the previous section.

<!-- βœ… These ARE valid for the first item -->
<p *ngIf="'Expression'"></p>
<p *ngFor="let i = index; ..."></p>

<!-- πŸ›‘ But these are NOT valid for the first item -->
<p *ngFor="of: ['🌱', 'πŸ€', '🌿']; ..."></p>
<p *ngFor="index as i"></p>
Enter fullscreen mode Exit fullscreen mode

2. Second Item and Beyond

After the first item, you’re able to pass in a let binding, an as binding, or a key expression.

Let's revisit these *ngFor syntaxes at the very beginning of this post:

1. let leaf of ['🌱', 'πŸ€', '🌿']; let i = index;
2. let leaf; let i = index; of ['🌱', 'πŸ€', '🌿'];
3. let i = index; let leaf; of ['🌱', 'πŸ€', '🌿'];
4. let i = index let leaf; of: ['🌱', 'πŸ€', '🌿'];
5. let i = index; of ['🌱', 'πŸ€', '🌿']; let leaf;
6. let i = index; of: ['🌱', 'πŸ€', '🌿'] let leaf;
7. 'Magic πŸͺ„'; let i = index; of: ['🌱', 'πŸ€', '🌿']; let leaf;
8. let leaf = $implicit of ['🌱', 'πŸ€', '🌿']; let i = index;
9. let leaf = $implicit; let i = index; of ['🌱', 'πŸ€', '🌿'];
10. let leaf = $implicit, let i = index, of ['🌱', 'πŸ€', '🌿'];
Enter fullscreen mode Exit fullscreen mode

In fact, these *ngFor syntaxes end up with the same result:

<ng-template ngFor let-leaf [ngForOf]="['🌱', 'πŸ€', '🌿']" let-i="index">
  ...
</ng-template>
Enter fullscreen mode Exit fullscreen mode

Optional Separators

One side note, maybe you've already figured out from these *ngFor syntaxes above.
Just as the : is optional in the a key expression, all separators in the micro syntax are optional.


πŸ‘‹ Hey there

If you want to gain more insights into how Angular Structural Directives internals, you can reference my previous blog post

Demystifying the Angular Structural Directives in a nutshell


Thank you for making it to the end

Thank you for choosing to read this blog post among the tens of thousands of great blog posts out there. And I will be really happy if you find something interesting from my blog post.


Reference

Angular Templates β€” From Start to Source

πŸ’– πŸ’ͺ πŸ™… 🚩
khangtrannn
Khang Tran ⚑️

Posted on November 3, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related