Angular structural directives and their microsyntax
Tags: angular, webdev, tutorial
Published: 2022-05-30 20:31:11

Angular structural directives and their microsyntax

{% embed https://youtu.be/X7x3Mxj5vQg %}
_See this and many other articles at [lucaspaganini.com](lucaspaganini.com)_

Have you ever wondered what's that star prefix for `*ngIf` and `*ngFor`? That's called a structural directive.

In this article, I'll show you what it is when you would want it and how it works.

I’ll also do a part 2, showing you how to create your own structural directives.

## Templates are the structure

Let’s start defining what it is.

A structural directive is a directive with a structure. The structure is an ng-template.
When you write `<div><p>Text</p></div>`, you’re telling Angular to “_declare the structure of a div tag, with a paragraph tag, with the string “Text”, and render it_”.

But when you wrap it in an `<ng-template><div><p>Text</p></div></ng-template>`, you’re telling Angular to “_declare the structure of a div tag, with a paragraph tag, with the string “Text”_”. But notice that now we’re not telling Angular to render it.

Now, put a directive in the `<ng-template>` and you have a structural directive:
`<ng-template [ngIf]=“condition”><div><p>Text</p></div></ng-template>`

## Syntactic sugar

That's how ngIf works. Angular parses the `<ng-template>`, generating a TemplateRef, which is injected in the NgIf directive. If the condition passed to ngIf is true, the template is rendered.

But it would be very annoying to create an ng-template every time we wanted to use NgIf or any other directive that requires an ng-template. So the Angular team created syntactic sugar. Like a shortcut.

When you prefix your directive with a star, Angular wraps it in an ng-template and applies the directive to the ng-template. So `<div *ngIf=“condition”>Abc</div>`, becomes `<ng-template [ngIf]=“condition”><div>Abc</div></ng-template>`

It’s just syntactic sugar. You could write your whole app without the star prefix if you wanted.

## Only one allowed

Knowing how it works, you can now understand why we can only use one structural directive per element. If you were to use `*ngIf` and `*ngFor` in the same element, how would Angular desugar that? ngIf first and then ngFor? The reverse? Both in the same template?

## Microsyntax

Talking about ngFor, it seems much more complicated than ngIf, right? I've seen some really complex ngFor expressions, like passing a trackBy function, piping an observable array, grabbing the index, and checking if it’s the last element.

```ts
<div *ngFor="let item of list$ | async; trackBy: trackByFn; let itemIndex = index; let islast = last">{{ item }}</div>
```

Initially, I thought that was a ngFor-specific lingo, but it's not. It's a fully documented syntax that works for any structural directives, even ones that you end up creating. It's called the "structural directive microsyntax". (kinda obvious)

The structural directive microsyntax divides expressions by semicolons (;). In our NgFor example, we'd have 4 expressions:

1. let item of list$ | async
2. trackBy: trackByFn
3. let itemIndex = index
4. let islast = last

## Declarations

Expressions starting with `let` are variable declarations. You declare the variable name right after `let` and use the equal sign (=) to point it to the name of the variable in the exported directive context.

That was a lot, sorry.

What I mean is that when we render an `<ng-template>`, we can optionally pass a context object. And the properties of this context object are passed to the template. The context object can have multiple explicit variables and a single implicit variable.

```ts
<!-- Rendering an <ng-template> with a context object -->
<ng-container *ngTemplateOutlet="templateExample; context: { $implicit: 'test', index: 1 }"></ng-container>

<!-- Using the context properties in the <ng-template> -->
<ng-template #templateExample let-itemIndex="index" let-item>
  <p>#{{ itemIndex }} - {{ item }}</p>
</ng-template>
```

It's like a JavaScript function, we have the parameters, which we declare and thus are very explicit, and we have `this` which is an implicit variable that exists even though we haven't declared it.

```ts
function example(itemIndex, isLast) {
  // Explicit
  console.log(itemIndex, isLast);

  // Implicit
  console.log(this);
}
```

In a function, you can have as many parameters as you want, but only one `this`. Just like that, in an ng-template, you can have as many explicit variables as you want, but only one implicit variable.

The implicit variable is what you get when you don't point to any exported variable. `let item` for example, is getting the implicit variable. But `let isLast = last` is getting the explicit `last` variable and `let itemIndex = index` is getting the explicit `index` variable.

After desugaring the variables, that's what we get:

```ts
<ng-template let-item let-itemIndex="index" let-isLast="last">
    <p>#{{ itemIndex }} - {{ item }}</p>
    <p *ngIf="isLast">The end</p>
</ng-template>
```

## Key expressions

Expressions with two arguments and an optional colon (:) between them are key expressions. The expression (in the right) gets assigned to the key (in the left) with a prefix before it.

Let's look at some examples.

In `\*ngIf="condition; else otherTemplate`, for the `else otherTemplate` expression:

- ngIf is the prefix
- else is the key
- otherTemplate is the expression

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3dh61c0mz76gx0xiw3mc.png)
 

That gets desugared to `<ng-template [ngIfElse]="otherTemplate"></ng-template>`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h1oq73qnro1yfjhxwehx.png)
 

In `*ngFor="let item of list; trackBy: trackByFn`, for the `trackBy: trackByFn` expression:

- ngFor is the prefix
- trackBy is the key
- trackByFn is the expression

That gets desugared to `<ng-template [ngForTrackBy]="trackByFn"></ng-template>`

Also, for that NgFor example, `of list` in `let item of list` is ALSO a key expression.

- ngFor is the prefix
- of is the key
- list is the expression

That gets desugared to `<ng-template [ngForOf]="list"></ng-template>`

## Local bindings

The last thing to mention is the optional `as` keyword at the end of the expression. It declares a template variable and maps the result of the expression to it.

`*ngIf="condition as value"` becomes `<ng-template [ngIf]="condition" let-value="ngIf">`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0dj6z6z9xs989sorm38i.png)
 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0ucuoiu2u3l2085d44m.png)
 

## Conclusion

That's it. You now understand how structural directives work and how to analyze their microsyntax.

I'll do another article on how to code a custom structural directive from scratch and how to tell the Angular compiler to type-check its context.

Have a great day and see you soon!

## References

1. [Structural directives documentation _Angular docs_ ](https://angular.io/guide/structural-directives)
2. [NgIf Directive implementation _GitHub_](https://github.com/angular/angular/blob/12.0.0/packages/common/src/directives/ng_if.ts#L10-L239)