Mastering Angular Templates
Tags: angular
Published: 2024-02-05 20:27:28

Hello Devs,

If you're an Angular developer, chances are you've worked with PrimeNG. I've been using it for some time, and there's always been one aspect that caught my attention: TEMPLATES!

Take, for instance, the PrimeNG Card component. This component provides a **_header_** property where you can pass a string, but the real power lies in customizing the header to your liking using a **_pTemplate_**. It functions as illustrated below:

```html
<p-card header="Car Header">
    <ng-template pTemplate="header">
        <img alt="Card" src="http://fake.url.png" />
    </ng-template>
</p-card>
```

That's pretty awesome, right?
If you're as curious as I am, you probably want to dive into the inner workings of this template feature. No worries, my friend – I've got your back. I've explored and figured it out, and now you can too. Here's how it works, and you'll be able to apply this strategy in your components as well.

The core idea is to capture the content inside the _**ng-template**_ and render it at a specific location within our component. But not just any template – we want a specific one. In the example below, our aim is to render only the template with the directive **_myTemplate="header"_** in our component:

```html
<my-component header="Car Header">
  <ng-template myTemplate="header">
    <button>Simple button</button>
  </ng-template>
</my-component>
```

First things first, let's create the _**myTemplate**_ directive:

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

@Directive({
  selector: '[myTemplate]',
  host: {},
})
export class MyTemplateDirective {
  @Input() type: string | undefined;

  @Input('myTemplate') name: string | undefined;

  constructor(public template: TemplateRef<any>) {}

  getType(): string {
    return this.name!;
  }
}
```

As we can see, it's a straightforward structural directive (more on structure directives here: [Angular.io](https://angular.io/guide/structural-directives#creating-a-structural-directive)) with a single input to receive the type (in our case, "header"), and a function named _**getType**_ that returns the input value (I'll explain the reason for this function later in the article).

Now, let's move on to creating the component _**<my-component></my-component>**_.

```javascript
import {
  AfterContentInit,
  Component,
  ContentChildren,
  QueryList,
  TemplateRef,
} from '@angular/core';
import { MyTemplateDirective } from '../my-template-directive/my-template-directive';

@Component({
  selector: 'my-component',
  templateUrl: './my-component.component.html',
  styleUrl: './my-component.component.scss',
})
export class MyComponent implements AfterContentInit {
  headerTemplate: TemplateRef<any> | undefined;

  @ContentChildren(MyTemplateDirective) templates:
    | QueryList<MyTemplateDirective>
    | undefined;

  ngAfterContentInit() {
    (this.templates as QueryList<MyTemplateDirective>).forEach((item) => {
      switch (item.getType()) {
        case 'header':
          this.headerTemplate = item.template;
          break;

        default:
          break;
      }
    });
  }
}
```

Let's break down this component:

1) We require a property (_**headerTemplate**_) with the type _**TemplateRef**_ to store the template that is being passed to the component.
2) We need a property (_**templates**_) to store all the templates that have our custom directive assigned to them. In our case, this property is 'templates' and is decorated with _**@ContentChildren(MyTemplate)**_ (you can find more about the _**@ContentChildren**_ decorator [Angular.io](https://angular.io/api/core/ContentChildren))
3) With the help of the lifecycle hook _**AfterContentInit**_ (you can learn more about the _**AfterContentInit**_ lifecycle hook [Angular.io](https://angular.io/api/core/AfterContentInit)), we iterate over all the templates and call the function _**getType**_ to retrieve only the template with the directive "header," storing it in the _**headerTemplate**_ property.

That wraps up the TypeScript part of our component. Essentially, we're searching for all _**ng-templates**_ with our custom directive that were passed to _**MyComponent**_ and extracting only the one with the 'header' input. 

Now that we have the template in the _**headerTemplate**_ property, let's proceed to display it in the HTML. Take a look at the code below:

```html
<div>
  <div>
    <div *ngIf="headerTemplate">
      <div>Template content</div>
      <ng-container *ngTemplateOutlet="headerTemplate"></ng-container>
    </div>
  </div>
  <div>--------------------------</div>
  <div>
    <div>Footer without template</div>
  </div>
</div>
```

It's as simple as that. By utilizing the Angular structural directive _**ngTemplateOutlet**_ (you can find more information on _**ngTemplateOutlet**_ [Angular.io](https://angular.io/api/common/NgTemplateOutlet)) we use an **_ng-container_** to pass our **_headerTemplate_** as input with '*ngTemplateOutlet="headerTemplate"'. This allows us to dynamically render the template within our component.

This strategy empowers us to build highly customizable and flexible components. By implementing this approach, we ensure a robust component that only renders templates specified by the custom directive. It provides a level of control and encapsulation, allowing developers to create versatile components while maintaining a clear structure and limiting the rendering scope to specific templates. This can lead to more maintainable and modular code.

What are your thoughts on this design?
In what ways could this design be improved or extended?

If you want to explore the code for this example, you can find it in the GitHub repository: [Angular Templates GitHub Repository](https://github.com/CaioRagazzi/mastering-angular-templates).

Thank you so much for reading this post, everyone! As always, I wish you happy coding!

See you!