The power of structural directives
Tags: angular, javascript
Published: 2020-06-29 19:22:38

**Structural directives are a very powerful tool in Angular, but up till now, I rarely used them. The build-in ones are suitable for nearly every use-case I faced so far. 
 But lately, I reimplemented the [*ngFor](https://angular.io/api/common/NgForOf) with a custom implementation of mine to implement an infinity scroll alternative. That was my personal "wow" effect and for the first time, I understood the full power of structural directives. This article should outline this power, but first, to the basics: What is a structural directive?**

## What is a structural directive?

I could not better phrase it then the angular documentation:

> Structural directives are responsible for HTML layout. They shape or reshape the DOM's structure, typically by adding, removing, or manipulating elements.

So basically: Every time you want to restructure something in the DOM you use a structural directive. The most commonly used ones are `*ngIf` or `*ngFor`, which represent a condition or a loop on the template. Here is an example of how you could use a `*ngIf` on an Angular template:

```html
<button (click)="toggle = !toggle">
  Show
</button>
<div *ngIf="toggle">
  I'm added or removed from the DOM with a structural directive.
</div>
```

Nothing really new about this. But, what mostly is unknown: You can build your **own** structural directive. Let's try to reimplement the `*ngIf` next.

## Writing you're own structural directive

Basically it's plain easy to write your own structural directive, as it is just a normal directive. You just need to create a directive:

```typescript
import { Directive } from '@angular/core';

@Directive({
  selector: '[superIf]'
})
export class SuperIfDirective {
  constructor() {
    console.log('init');
  }
}
```

And you can already add it as a structural directive **and** as a normal directive:

```html
<div *superIf>Test</div>
<div superIf>Test</div>
```

When you try that plain example, you will find only one `Test` output on your app. But the directive was actual initialized two times:

{% stackblitz angular-onbhnc view=both file=src/app/app.component.html %}

That is because the `*` is syntactic sugar which will wrap the component where this element is attached within the template. So, in fact the following is the same:

```html
<ng-template [superIf]>
  <div>Test</div>
</ng-template>
```

You can verify this by using our above `*ngIf` example. If you change the `*ngIf` to use the above layout, it will still work the same:

{% stackblitz angular-kmmhk1 view=both file=src/app/app.component.html %}

That's basically all of the mystery of structural directives. They are just syntax sugar for writing cleaner templates. As soon as you have access to the `ng-template` you can manipulate the DOM to your need. The next chapter will show how you roll your own ngIf.

## Building your own ngIf
It's really not that hard to build your own ngIf as soon as you understand the basics: A structural directive is syntactic sugar for a directive that is wrapped in an `ng-template`. That is why you can simply inject the template reference to your directive and use it to attach it to your view. 

First, we need to add the `TemplateRef` and `ViewContainerRef`:

```typescript
import { Directive, TemplateRef, ViewContainerRef, Input } from '@angular/core';

@Directive({
  selector: '[superIf]'
})
export class SuperIfDirective {
  constructor(
    private tpl: TemplateRef<any>,
    private vcr: ViewContainerRef
  ) {}
}
```

The `ViewContainer` reference is the point, where your structural directive is placed in the current view. Think of it as an invisible placeholder, which you can append any template to. This is what we are doing in the next step:

```typescript
  set superIf(expression: boolean) {
    this.vcr.clear();  // 1
    if (expression) {  // 2
      this.vcr.createEmbeddedView(this.tpl);  // 3
    }   
  }
```

The setter with the same name as the directive will ensure that we get the value that is assigned to our directive. Then we simply always clear the view when the setter is called (1), afterward we check if the expression is truly (2) and if yes, we create the template into our view container "placeholder" (3). The result works exactly as the *ngIf we know of:

{% stackblitz angular-hjhtfj view=both file=src/app/superIf.directive.ts %}

Easy, right? You might know that there are more functions. For example, there is an `else` in the default `*ngIf`. Also, this is fairly easy to implement when you know of the ability to pass multiple values to a directive:

```typescript
@Input()
set superIfElse(template: TemplateRef<any>) {
  this.elseTemplate = template;
}
```

This allows you to pass an else template to the directive either with the structural directive micro syntax: `<div *superIf="toggle; else notTrue">`. 
Or, as this is just sugar, we can also use the directive approached on a template: `<ng-template [superIf]="toggle" [superIfElse]="notTrueDirective">`

What is left is to check if the else template is set. If it is, and the expression is false, we attach this template instead of the one the directive is placed on:

```typescript
  @Input()
  set superIf(expression: boolean) {
    this.vcr.clear();
    if (expression) {
      this.vcr.createEmbeddedView(this.tpl);
    } else if(this.elseTemplate) {
      this.vcr.createEmbeddedView(this.elseTemplate);
    }
  }
```

That's all, you now created an `*ngIf` replacement. You can see the full example [here](https://stackblitz.com/edit/angular-hghkc9?file=src%2Fapp%2FsuperIf.directive.ts). If this is useful? I don't think so. Why reinventing the wheel? But it's super useful to understand the concept of structural directives. Next up we will build an actual useful directive.

## Doing something useful
So rebuilding the build-in directives is nice for understanding the concept but it doesn't bring any benefit. This chapter tries to implement something easy and at the same time useful. 
Therefore we are going to develop an **usefulAssign** directive which should allow to write cleaner templates. The problem is mostly known to Angular developers: When you request an observable which resolves to an object you often end up in reusing the async pipe over and over again:

```html
<div>
  min: {{(interval$ | async).min}}<br />
  s: {{(interval$ | async).s}}<br />
  ms: {{(interval$ | async).ms}}
</div>
```
That looks strange and gets very confusing soon. The idea is to use a directive for this to bind to an implicit variable. Then you can use this variable instead of reusing the async pipe over and over again:

```html
<div *usefulAssign="interval$ | async; let timer">
  min: {{timer.min}}<br />
  s: {{timer.s}}<br />
  ms: {{timer.ms}}
</div>
```

The result: Much cleaner templates and less async pipe usage. The implementation is quite easy, we just need to add a context object to the `createEmbeddedView` function and the first implicit variable gets the `value` assigned. 

The `$implicit` is a special type of context, which doesn't need an assignment. You can add more which need to be assigned in the template (e.g. `interval$ | async; let foo = bar`. If `bar` is defined in the context as `{ bar: 'foobar' }` then the foo variable contains the `foobar` string).

The directive itself then looks like this:

{% stackblitz angular-d9dvdv view=both file=src/app/usefulAssign.directive.ts %}

As you can see in this example, the value passed to the directive is assigned as an implicit context (1) and therefore available in the template. This allows many different approaches as you can pass any context which you can then reuse in the template easily.

## What else?
So this article is just a short introduction and should show what structural directives can do. This is just the tip of the iceberg and some more ideas are:
 
 - Using it as an infinity scroll solution in combination with paged REST API and the [Intersection Observable API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). Imagine using `*infinityFor="let image of images"` and you get infinity scrolling without doing anything more, by simply using the same approach as `*ngFor` and an additional hidden "load-more" component at the end of the list. This loads more data as soon as it is intersected.
 - Templating for more complex components like tables (like Material CDK does it).
 - dynamic components loading

However, it is always questionable to reinvent the wheel. Don't use a custom structural directive, if a build-in can do the same job.