Superpowers with Directives and Dependency Injection: Part 2
Tags: angular, javascript, typescript
Published: 2023-03-16 07:51:31

_Original cover photo by [Markus Spiske](https://unsplash.com/@markusspiske) on Unsplash._

In the [previous post](https://dev.to/this-is-angular/superpowers-with-directives-and-dependency-injection-part-1-ho7) we took a look at how we can use dependency injection + directives to both simplify our templates *and* achieve reusability. In this one, we are going to explore structural directives and how we can make components and directives interoperate and reduce clutter in our `.html` files even further.

Let's build a loader component!

## The Use Case

Imagine the following scenario: we have a component that loads some data, and we want to show a loading indicator while the data is being fetched. The following criteria should be met:

1. We should be able to wrap any template inside our loader component, and it will display a spinner when needed
2. The component should receive an input property indicating whether the data is being loaded or not
3. The template should be covered by an overlay, so that the user cannot interact (and potentially trigger other HTTP calls) while the data is being loaded

Here is a pretty simple implementation:

```typescript
@Component({
  selector: 'app-loader',
  template: `
    <div class="loading-container">
      <ng-content/>
      <div *ngIf="loading" class="blocker">
        <p-progressSpinner/>
      </div>
    </div>`,
  standalone: true,
  styles: [
    `
      .loading-container {
        position: relative;
      }
      .blocker {
        background-color: black;
        position: absolute;
        top: 0;
        z-index: 9999;
        width: 100%;
        height: 100%;
        opacity: 0.4;
      }
    `,
  ],
  imports: [NgIf, ProgressSpinnerModule],
})
export class LoaderComponent {
  @Input() loading = false;
}
```

> *Note*: I am using [PrimeNG](https://primeng.org/) for the examples in this article, but you can easily reuse them with any other implementation

So, here we just project any content that we receive into `ng-content` and the rest is some simple CSS + PrimeNG `ProgressSpinner` component. The `loading` input property is used to toggle the spinner on and off.

Now we can use it in the template as follows:

```html
<app-loader [loading]="loading">
  <p>Some content</p>
</app-loader>
```

> Wait, I thought this article is about directives?


Well, good news: it *is* about directives. But what's the problem with the component? Well, the example of its usage we saw was quite optimistic: real-life scenarios usually are not that simple. Consider this piece of template:

```html
<app-loader [loading]="loading">
  <div class="p-grid">
    <div class="p-col-12">
      <p>Some content</p>
    </div>
    <app-loader [loading]="otherLoading">
        <div class="p-col-12">
            <p>Some other content</p>
            <app-loader [loading]="evenMoreLoading">
                <div class="p-col-12">
                    <p>Even more content</p>
                </div>
            </app-loader>
        </div>
    </app-loader>
  </div>
</app-loader>
```

Now, here, when we have some nested elements, and the template keeps unnecessarily growing, adding more indentation levels, more closing tags, and so on, and so on. What I personally would really love to be able to do is the following:

```html
<p *loading="loading">Some content</p>
```

But how can we achieve this? Well, we need a directive that does the following things:

1. Creates a `LoaderComponent` instance dynamically
2. Somehow projects the nested template into it
3. Keeps them in sync - when the `loading` input property changes, the directive should update the `LoaderComponent` instance accordingly
4. Render the whole stuff

Let's dive into it!

## Structural Directives

[Structural directives](https://angular.io/guide/structural-directives) are really cool, because they allow us to reference some templates via [`TemplateRef`](https://angular.io/api/core/TemplateRef) and do all sorts of magic with it.  
Also, we can use the [`ViewContainerRef`](https://angular.io/api/core/ViewContainerRef) to create components dynamically. What will be left for us is to project the template into the component, And yes, *this is possible!* Let's start simple:

```typescript
@Directive({
  selector: '[loading]',
  standalone: true,
})
export class LoaderDirective {
  private readonly templateRef = inject(TemplateRef);
  private readonly vcRef = inject(ViewContainerRef);
  @Input() loading = false;
  templateView: EmbeddedViewRef<any>;
  loaderRef: ComponentRef<LoaderComponent>;
}
```

Here we injected the things we need (`TemplateRef` and `ViewContainerRef`), added a `loading` input, naturally, and created two properties: `templateView` and `loaderRef`. The first one will be used to store the reference to the template that we get, and the second one will be used to store the reference to the `ComponentRef` instance that we are going to create - we have to store both.

Next, left do some initial heavy lifting to set the whole thing up:

```typescript
@Directive({
  selector: '[loading]',
  standalone: true,
})
export class LoaderDirective implements OnInit {
  private readonly templateRef = inject(TemplateRef);
  private readonly vcRef = inject(ViewContainerRef);
  @Input() loading = false;
  templateView: EmbeddedViewRef<any>;
  loaderRef: ComponentRef<LoaderComponent>;

  ngOnInit() {
    this.templateView = this.templateRef.createEmbeddedView({});
    this.loaderRef = this.vcRef.createComponent(LoaderComponent, {
      injector: this.vcRef.injector,
      projectableNodes: [this.templateView.rootNodes],
    });

    this.loaderRef.setInput('loading', this.loading);
  }
}
```

Here, our `ngOnInit` lifecycle method does four things:

1. Make the template into an embedded view, so we can render it dynamically
2. Create a `LoaderComponent` instance
3. Project the template into the `LoaderComponent` instance via `projectableNodes` - here is where the magic is happening!
4. Set the `loading` input property on the `LoaderComponent` instance 

Now, this way it will kinda work, but we need two more things to make it work properly:

1. We need to update the `LoaderComponent` instance when the `loading` input property changes
2. We need to ensure change detection still works on the projected template despite it being "detached" from the parent view and projected into a new component. We will use `ngDoCheck` for this

Let's finalize the implementation:

```typescript
@Directive({
  selector: '[loading]',
  standalone: true,
})
export class LoaderDirective implements OnInit, DoCheck, OnChanges {
  private readonly templateRef = inject(TemplateRef);
  private readonly vcRef = inject(ViewContainerRef);
  @Input() loading = false;
  templateView: EmbeddedViewRef<any>;
  loaderRef: ComponentRef<LoaderComponent>;

  ngOnInit() {
    this.templateView = this.templateRef.createEmbeddedView({});
    this.loaderRef = this.vcRef.createComponent(LoaderComponent, {
      injector: this.vcRef.injector,
      projectableNodes: [this.templateView.rootNodes],
    });

    this.loaderRef.setInput('loading', this.loading);
  }

  ngOnChanges() {
    this.loaderRef?.setInput('loading', this.loading);
  }

  ngDoCheck() {
    this.templateView?.detectChanges();
  }
}
```

Those additions are fairly simple: when the `loading` property of the component changes, we update the `LoaderComponent` instance accordingly, and when there is change detection running for the directive instance, we also notify the child template in the `ngDoCheck` lifecycle method via `templateView.detectChanges()`. If you are unfamiliar with how `ngDoCheck` works or why it is used, you can read [the official docs](https://angular.io/api/core/DoCheck), or [this tutorial](https://www.tektutorialshub.com/angular/angular-ngdocheck-life-cycle-hook/).

Now we can simply use it in the template, even when we have multiple nested elements:

```html
<p *loading="loading">
    Some content
    <span *loading="otherLoading">
        Some other content
    </span>
    <p *loading="evenMoreLoading">
        Even more content
    </p>
</p>
```

And so, there is no nested template, no unnecessary indentation, and no unnecessary closing tags. It's just a simple directive that does the job.

You can view the full example with a live demo on StackBlitz:

{% embed https://stackblitz.com/edit/angular-yjmfn1?embed=1&file=src/main.ts&ctl=1 %}

## Conclusion

As mentioned previously, I believe directives are very, **very** powerful, but sadly underused in the wider community. With this series of articles I want us to explore different use cases where directives help us simplify our templates and improve readability. In the next piece, we will explore using directives to hack into existing components. Stay tuned!