<p *>Demystifying the Angular Structural Directives in a nutshell</p>
Tags: angular, frontend, sharing, learning
Published: 2023-10-15 13:13:53

### 1. Introduction
I'm sure you're definitely familiar with Angular's commonly used structural directives that we use all the time: `ngIf`, `ngFor`, and `ngSwitch`. But have you ever wondered about the _✨magic✨_ behind that little _asterisk_ _\(\*\)_? In this post, I'll take you on a journey to demystify the power of _Angular's micro syntax_. Let's dive right in.

> <strong>Let's begin by trying something _NEW_ that you've probably never done before</strong>

🔥 <strong>Using <em>\*ngIf</em> WITHOUT <em>asterisk \(\*\)</em></strong>
```javascript
<div ngIf="true">Does it really work 🤔?</div>
```

If you try it on your own, you'll quickly discover that, unfortunately, it doesn't work, and the console will throw this error:

![No provider for TemplateRef found](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oo7i4gsvk396xka6ihn1.png)

<em>❗Error: NG0201: No provider for **_TemplateRef_** found.</em>

So what exactly is `TemplateRef` and why does removing the <em>asterisk \(*\)</em> from the `ngIf` directive trigger this error? <em>(The answer awaits you in the upcoming section)</em>

🔥 <strong>What happens if we use only the <em>asterisk \(\*\)</em>, as in the headline of this post?</strong>
```javascript
<p *>Demystifying the Angular Structural Directives 
in a nutshell</p>
```

Perhaps you'll experience the same sense of surprise I did when I first tried this: no errors, and nothing rendered in the view. It sounds like magic, doesn't it? So WHY does this happen??? 🐣

> **_Everything happens for a reason._**

Actually, the above syntax is just the shorthand _(syntax desugaring)_ of `<ng-template>`. This convention is the shorthand that `Angular` interprets and converts into a longer form like the following:

```javascript
<ng-template>
  <p>Demystifying the Angular Structural Directives 
in a nutshell</p>
<ng-template>
```
And based on the `Angular` [_documentation_](https://angular.io/api/core/ng-template), the `<ng-template>` is not rendered by default.</em>

![Angular <ng-template> documentation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9z3s94bskomyl7edsj6e.png)

<hr />

> `*ngIf` and `*ngFor` behave in a similar fashion. `Angular` automates the handling of `<ng-template>` behind the scenes for these directives as well.

```javascript
// Shorthand
<div *ngIf="true">Just say hello</div>

// Long form
<ng-template [ngIf]="true">
  <div>Just say hello</div>
</ng-template>
```
```javascript
// Shorthand
<span *ngFor="let greenyPlant of ['🌱', '🌿', '🍀']">
  {{greenyPlant}}
</span>

// Long form
<ng-template ngFor let-greenyPlant [ngForOf]="['🌱', '🌿', '🍀']">
  <span>{{greenyPlant}}</span>
</ng-template>
```

You might be curious because, according to the documentation, `<ng-template>` isn't rendered by default, and we need to _specifically instruct_ it to do so. So, what exactly does _specifically instruct_ mean, and how do `*ngIf` and `*ngFor` handle the rendering of `<ng-template>`?

<hr />

### 2. Create our own custom structural directive
> <strong>Delve into Angular <em>NgIf</em> [source code](https://github.com/angular/angular/blob/main/packages/common/src/directives/ng_if.ts)</strong>


![Angular's NgIf directive source code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v8yq973mygcxhsh40iwa.png)

The first surprising detail, which might easily go unnoticed without a deeper look into the `NgIf` source code, is the absence of the _asterisk \(\*\)_ symbol in the `NgIf` selector. Instead, it ONLY uses plain `'[ngIf]'` as the selector. (the same applies to other _Angular structural directives_ as well)

From the constructor, you'll find `TemplateRef` and `ViewContainerRef`. These are the two key elements required for rendering the template.

> **_TemplateRef_**

First of all, we need the necessary information to render the template to the DOM, here is where `TemplateRef` comes into play. You can think of a `TemplateRef` as a blueprint for generating HTML template content.
```javascript
<ng-template>
  <p>Everything placed inside ng-template can be
    referenced by using TemplateRef</p>
</ng-template>
```
Let's take a look at the internal workings of the `TemplateRef`.
```javascript
@Directive({
  selector: '[greeting]',
  standalone: true,
})
export class GreetingDirective implements OnInit {
  #templateRef = inject(TemplateRef);

  ngOnInit(): void {
    console.log((this.#templateRef as any)._declarationTContainer.tView.template);
  }
}

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule, GreetingDirective],
  template: `
    <p>Demystifying the Angular Structural Directives in a nutshell</p>
    <ng-template greeting>Xin chào - Hello from Viet Nam 🇻🇳!</ng-template>
  `,
})
export class App {}
```

Under the hood, Angular will translate the `<ng-template>` into the _TemplateRef's instructions_, which we can later use to dynamically render the content of the `<ng-template>` into the view.

```javascript
function App_ng_template_2_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵtext(0, "Xin ch\u00E0o - Hello from Viet Nam \uD83C\uDDFB\uD83C\uDDF3!");
} }
```

> **_ViewContainerRef_**

Until now, we know how `Angular` interprets _TemplateRef's instructions_, but how does `Angular` handle the process of _hooking_ the template into the view?

Let's return to the previous example and inspect the DOM element.
```html
<!DOCTYPE html>
<html class="ml-js">
  <head>...</head>
  <body>
    <my-app ng-version="16.2.8">
      <!--container-->
    </my-app>
  </body>
</html>
```
You'll notice that there is a special comment markup `<!--container-->`. This is exactly where the template will be inserted into the view.

Specifically, it is a _**comment node**_ that acts as an anchor for a view container where one or more views can be attached. ([_view_container_ref.ts_](https://github.com/angular/angular/blob/main/packages/core/src/linker/view_container_ref.ts))

![Angular ViewContainerRef source code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rdxlzt1o16wdjihd74wx.png)

Let's make a minor update to the `GreetingDirective` to see how it works.

```javascript
@Directive({
  selector: '[greeting]',
  standalone: true,
})
export class GreetingDirective implements OnInit {
  #vcr = inject(ViewContainerRef);

  ngOnInit(): void {
    console.log(this.#vcr.element.nativeElement);
  }
}
```
You'll get exactly `<!--container-->` comment node from the console.

![\<!--container--\> demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5o3dx4muspsg5jt3k10c.png)

So let's put everything together by rendering our `<ng-template>` into the view.

```javascript
@Directive({
  selector: '[greeting]',
  standalone: true,
})
export class GreetingDirective implements OnInit {
  #templateRef = inject(TemplateRef);
  #vcr = inject(ViewContainerRef);

  ngOnInit(): void {
    this.#vcr.createEmbeddedView(#templateRef);
  }
}
```

The final piece is `createEmbeddedView`. It essentially tells `Angular` to render the content defined in the `TemplateRef` and insert it within the element that the _ViewContainerRef_ is associated with.

![<ng-template> demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/517pbbs6pk0zj8iu7we9.png)

_[Link to Stackbliz](https://stackblitz.com/edit/stackblitz-starters-2nka4p)_

> **Can we do it better?**

You may wonder if there is a way to automatically render the `<ng-template>` instead of handling it manually so tedious. You're right; let's delegate the work to `*ngTemplateOutlet`.

```javascript
@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule, NgTemplateOutlet],
  template: `
    <ng-container *ngTemplateOutlet="greetingTemplate"></ng-container>
    <ng-template #greetingTemplate>Xin chào - Hello from Viet Nam 🇻🇳!</ng-template>
  `,
})
export class App {}
```
The following is a snippet from the [_source code_](https://github.com/angular/angular/blob/main/packages/common/src/directives/ng_template_outlet.ts) of `ng_template_outlet.ts`. It handles exactly as the whole story we discovered above.

```javascript
@Directive({
  selector: '[ngTemplateOutlet]',
  standalone: true,
})
export class NgTemplateOutlet<C = unknown> implements OnChanges {
  private _viewRef: EmbeddedViewRef<C>|null = null;

  ngOnChanges(changes: SimpleChanges) {
    ...

    if (this._shouldRecreateView(changes)) {
      // Create a context forward `Proxy` that will always bind to the user-specified context,
      // without having to destroy and re-create views whenever the context changes.
      const viewContext = this._createContextForwardProxy();
      this._viewRef = viewContainerRef.createEmbeddedView(this.ngTemplateOutlet, viewContext, {
        injector: this.ngTemplateOutletInjector ?? undefined,
      });
    }
  }
}
```

> **_Let's create a more complex directive using NASA Open APIs_**

![NASA Planetary Directive Demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0811djzalldx792mtmkm.png)

We all know to render the `TemplateRef` within the view, we simply need to use the `createEmbeddedView` from `ViewContainerRef`. However, what if we want to pass the data from our custom directive to the `TemplateRef`? The answer is `Template Context`.

> **Template Context**

_Inside the `<ng-template>` tags you can reference variables present in the surrounding outer template. Additionally, a context object can be associated with `<ng-template>` elements. Such an object contains variables that can be accessed from within the template contents via template (let and as) declarations. ([<ng-template> context](https://angular.io/api/core/ng-template#context))_

> **Understand template context with _NgFor_**

`I have no special talents. I am only passionately curious. - Albert Einstein`

At the very beginning, when I first attempted to use `NgFor`, I just accepted the fact that we can use `index`, `odd`, `even`, and other _data-binding context_ without wondering _where_ they came from. It's a bit clearer to me now 🥰.

```javascript
<div *ngFor="let greenyPlant of ['🌱', '🌿', '🍀']; let i = index"
  [class.even]="even" [class.odd]="odd">
  {{greenyPlant}}
</div>
```
In addition to the `index`, `even`, `odd`, you can reference all `NgFor` local variables and their explanation through [_NgFor documentation_](https://angular.io/api/common/NgFor#local-variables).

> **It's time to create our NASA Planetary Directive**

We will create a custom structural directive to fetch the _Astronomy Picture of the Day_ from [_NASA Open APIs_](https://api.nasa.gov/) and make the response data, including the _title_, _image_, and _explanation_, available through the template context for later use in our template.
```javascript
interface NASAPlanetary {
  hdurl: string;
  title: string;
  explanation: string;
}

@Directive({
  selector: '[nasaPlanetary]',
  standalone: true,
})
export class NASAPlanetaryDirective implements OnInit {
  #templateRef = inject(TemplateRef);
  #vcr = inject(ViewContainerRef);
  #http = inject(HttpClient);

  ngOnInit(): void {
    this.#http.get<NASAPlanetary>('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY')
      .pipe(take(1))
      .subscribe(({ title, hdurl, explanation }) => {
        this.#vcr.createEmbeddedView(this.#templateRef, {
          title, hdurl, explanation,
        });
    });
  }
}
```

We can bind data to the template context by passing it as a second parameter of `createEmbeddedView`, as shown in the code above. You can find out more about it through the [_documentation_](https://angular.io/api/core/ViewContainerRef#createEmbeddedView).

> **Rendering Astronomy Picture of the Day into the view**
```javascript
@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule, HttpClientModule, NASAPlanetaryDirective],
  template: `
    <div *nasaPlanetary="
        let hdurl = hdurl; 
        let title = title; 
        explanation as explanation
      " 
      class="mt-5 d-flex justify-content-center">
      <div class="card" style="width: 18rem;">
      <img [src]="hdurl" class="card-img-top" [alt]="title">
      <div class="card-body">
        <h5 class="card-title">{{title}}</h5>
        <p class="card-text explaination">{{explanation}}</p>
      </div>
    </div>
  `,
  styles: [...],
})
export class App {}
```

You can use either `let` or `as` to reference the data-binding context. 

Indeed, the above code will be converted to `<ng-template>` long form as follows:

```javascript
<ng-template nasPlanetary let-hdurl="hdurl" let-title="title" let-explanation="explanation">
  ...
</ng-template>
```

Congratulations, you'll receive today's news from the **_S P A C E_** 🚀🌌.

> **Supporting Dynamic Date**

But what if we want to retrieve a picture for a specific date rather than today? Let's enable our directive to support dynamic date through the use of the `@Input` decorator.
```javascript
@Input('nasaPlanetary') date = new Date().toLocaleDateString('en-CA');
```

With this input property, you can specify a date in the format `YYYY-MM-DD` to fetch an image from a particular day. If no date is provided, the directive will default to today.

To ensure that this input property works seamlessly with our directive, we need to make sure that the input alias matches the directive selector, which is `nasaPlanetary` in our case. So that it is recognized as the _default input_ of the directive.

To fetch an image for a specific date, we'll need to modify the _NASA Open APIs_ endpoint by including the _date_ query parameter:
```javascript
`https://api.nasa.gov/planetary/apod?date=${this.date}&api_key=DEMO_KEY`
```

Finally, binding data to the directive can be achieved as follows:
```javascript
// The data-binding context remains unchanged
<div *nasaPlanetary="'1998-01-08'; ..."></div>
```

💡 _**Property bindding issue**_
Why can't we bind data to a directive input as we do with a normal component input?

```javascript
<div *nasaPlanentary [date]="'1998-01-08'">
  Property bidding issue
</div>
```

We'll end up with the following error:
```
Can't bind to 'date' since it isn't a known property of 'div'.
```

Let's examine the long version of the code above to understand the underlying reason.
```javascript
<div [date]="'1998-01-08'">
  <ng-template>Property bidding issue</ng-template>
</div>
```
Indeed, the _date property_ is applied to the `<div>` tag instead of `<ng-template>` itself. That's why `Angular` considers _date property_ as a property of the `<div>` tag, rather than the input of the directive. And this is exactly the reason why we got the error `Can't bind to 'date' since it isn't a known property of 'div'.`

> **Supporting loading template**

We can bind the default input of the directive by giving it the same name as the directive selector. But what if we want to add additional inputs to the directive? Let's achieve this by implementing support for a loading template.

Let's add a new input to support the loading template:
```javascript
@Input('nasaPlanetaryLoading') loadingTemplate: TemplateRef<any> | null =
    null;
```
If you notice, the above input follows the naming convention:

💡 `Directive input name = directiveSelector + identifier (first character capital)`

For the _identifier_, we can choose whatever we want. In our case, since we want to add this input to support loading purposes, I've named it `loading`.

So let's define the loading template and add it to the directive:
```javascript
<div *nasaPlanetary="'1998-01-08'; loading loadingTemplate; ...">

<ng-template #loadingTemplate>
  You can define whatever you want for the loading template.
  We will pass the loading template to the directive
  by using the #templateVariable.
</ng-template>
```

> **Final demo**

I believe that covers everything I've learned about _Angular Structural Directives_ that I'd like to share in this post. While it may not be the most practical example for real-life projects, I hope you can still find something interesting.

_[Link to Stackbliz](https://stackblitz.com/edit/stackblitz-starters-mz5nrg)_

![NASA Planetary Structural Directive DEMO](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/su9m2l5z3wslxp2omzc3.gif)

### 3. Final thought
> _The most important thing in writing is to have written. I can always fix a bad page. I can’t fix a blank one. - Nora Roberts_

Thank you for making it to the end! This is my very first blog, and I'm thrilled to have completed it. I'm even more delighted if you found something helpful in my blog post. I'd greatly appreciate hearing your thoughts in the comments below, as it would be a significant source of motivation for me to create another one. ❤️

<hr>

> Read More

[Master the Art of Angular Content Projection](https://dev.to/khangtrannn/master-the-art-of-customizing-angular-components-2a1e)

<br><br>
<hr>
> **References**

_[1. Angular documentation](https://angular.io/docs)_
_[2. Mastering Angular Structural Directives - The basics \(Robin Goetz\)](https://dev.to/this-is-angular/mastering-angular-structural-directives-the-basics-jhk)_
_[3. Unlocking the Power of ngTemplateOutlet - Angular Tiny Conf 2023 (Trung Vo)](https://www.youtube.com/watch?v=_v_MEm9iV-c)_
_[4. Structural Directives in Angular – How to Create Custom Directive (Dmytro Mezhenskyi)](https://www.youtube.com/watch?v=07CaGlbMPbw)_