ngTemplateOutlet
Tags: angular, typescript
Published: 2021-08-08 13:31:30

"Magic is just science that we do not understand yet" 
    ...Arthur C. Clarke

This article is part of what I call the magical directives series. In this series, we will unravel the mystery behind some interesting Angular directives. Afterwards, we can add this little magic to our tool box. I call them magical directives because they play a very important role in building reusable components across our Angular applications.

Below are the directives that we will be looking at in this series.

* ng-template
* ng-container
* ng-content
* __*ngTemplateOutlet__

######__links to other articles in this series are below__

#The ngTemplateOutlet

In our last article [ng-content](https://dev.to/valoni01/ng-magical-directives-series-ng-content-2gfk), We said that we are going to discuss the conditional content projection in this particular article. One of the major draw backs in using the `ng-content` is the ability to specify default content. What this means is that if a user does not provide the content for a select attribute, that particular content tag will be blank. This is the first problem the `*ngTemplateOutlet` helps to solve. The `*ngTemplateOutlet` helps to build configurable components and also helps in the insertion of common template in different sessions of our page.

Let us start with the simple use case for the `*ngTemplateOutlet`, which is the insertion of a template into different sessions of our page. for example, if you have a repeated icon across your page, instead of creating the image tag or repeating the host html that holds that image, you can create a template with the `<ng-template>` and then use the `*ngTemplateOutlet` to reference it in every session that you need to display the icon.

```
// Template to be used at multiple sessions in our application

  <ng-template #ourIcon>
     <img src="urImage.jpg" alt="someImge">
  </ng-template>

//reference the template across different sessions in our application

  <div>
    <div class="header">
      <ng-container *ngTemplateOutlet="ourIcon"> </ng-container>
       <h1>Our lovely page</h1>
    </div>

    <div class="body">
      <ng-container *ngTemplateOutlet="ourIcon"> </ng-container>
    </div>

    <div class="Footer">
      <ng-container *ngTemplateOutlet="ourIcon"> </ng-container>
    </div>
  </div>

```
In the example above, we have been able to use the icon template across multiple sessions in our page without re-writing the html, and if we ever want to change the image url, we just have to update the template without touching the sessions where it is used.

###The second use case, is for the conditional content projection


```
// books-view.html

<h2>Our Story Books..</h2>

<ng-container *ngTemplateOutlet="booksTemplate ? booksTemplate : defaultBooks;">
</ng-container>

<ng-template #defaultBooks>
  <div *ngFor="let bk of booklist" class="books-card_default">
    <h1>{{bk.name}}</h1>
    <p>{{bk.author}}  - {{bk.year}}</p>
 </div>
</ng-template>

```

```
  //books.view.ts
  import { Component, Input, OnInit, TemplateRef } from '@angular/core';

@Component({
  selector: 'app-books-view',
  templateUrl: './books-view.component.html',
  styleUrls: ['./books-view.component.scss']
})
export class BooksViewComponent implements OnInit {

  @Input() booksTemplate!:TemplateRef<any>
  @Input() booklist = [
    {name:'The young shall grow', author:'some persons', year:'1975'},
    {name:'Without a silver spoon', author:'some body', year:'1407'},
    {name:'Lara the sugar gurl', author:'everybody',year:'1947'},

  ];

  constructor() { }

  ngOnInit(): void {
  }

}

```
In sample code above, we have defined a view for our lists of books.
First in the `books-view` `.ts` file, we are expecting an input of type templateRef

```
  @Input() booksTemplate!:TemplateRef<any>
```

and in the `.html` file we defined another template called `defaultBooks`

```
<ng-template #defaultBooks>
  <div *ngFor="let bk of booklist" class="books-card_default">
    <h1>{{bk.name}}</h1>
    <p>{{bk.author}}  - {{bk.year}}</p>
 </div>
</ng-template>

```
and in our template, we created the template container for the view. 

```
<ng-container *ngTemplateOutlet="booksTemplate ? booksTemplate : defaultBooks;">
</ng-container>

```
This is simply saying that, hey! we are expecting an input of type templateRef called booksTemplate, If it is provided, then display it, else use the default defined template called the `defaultBooks`

Now, you can use the `books-view` in any part of our application in the following ways.

1. pass in your custom template.
```
  <app-books-view [booksTemplate]="myCustomeTemplate"></app-books-view>
``` 

2. Allow the default template to be used

```
  <app-books-view></app-books-view>
```

Unlike the `ng-content`, we have been able to provide default values to be projected. 

The `*ngTemplateOutlet` is super useful for creating configurable components. 

### Two examples of how we can utilize the power of `*ng-templateOutlet` in our books-view

1. create multiple templates for different use cases, eg. card view, table view, list view. e.t.c and allow the parent component that want to use our books-view to specify the type of view they want, else return default view. Here, we can even go further to automate the views depending on defined condition. Example, you can set the `hostlistener` to listen to screen sizes, or allow users to select the type of view that they want while you automatically switch between your defined templates (card view, table view, list view). 

2. We can also decide to allow the parent component to send in their own template like we have in the above.

Below is an example of a parent component using our `books-view` along side with customer templates 

```
<ng-template  #cardView >
   <div let *ngFor="let bk of books" class="books-card">
      <h1>{{bk.name}}</h1>
      <p>{{bk.author}}  - {{bk.year}}</p>
   </div>
</ng-template>

<ng-template #tableView >
  <table>
    <tr>
      <th>Title</th>
      <th>Author</th>
      <th>Year</th>
    </tr>
    <tr *ngFor="let bk of books">
      <td>{{bk.name}}</td>
      <td>{{bk.author}}</td>
      <td>{{bk.year}}</td>
    </tr>
  </table>
</ng-template>

//passing the template to our app-view
<app-books-view [booksTemplate]="cardView"></app-books-view>

```

As seen above, we have two templates defined by our parent component, the cardView and the tableView. Although we have manually assigned the cardView to the booksTemplate, but we can always dynamically change the value from the `.ts` file of our parent component as discussed earlier.

Hey guys!.. Isn't that super cool?

But wait..  If you look at the `books-view.ts` you will see that we have default data (booklist) being used. What if we want to send in our own data but still want to use the default template?.

If you look at the default template, you will notice that it directly has access to the `booklist` in our `.ts` file. And that is the same for every template embedded in the `ng-template` they can access variables in our `.ts`. We can further create a unique variable that is only accessible to our template alone and not outside of it. This is done by using `let` keyword as in `let-[data]`.

Example:

```
<ng-container *ngTemplateOutlet="booksTemplate ? booksTemplate : defaultBooks; context:{$implicit:booklist}">

</ng-container>

<ng-template let-books #defaultBooks>
  <div *ngFor="let bk of books" class="books-card_default">
    <h1>{{bk.name}}</h1>
    <p>{{bk.author}}  - {{bk.year}}</p>
 </div>
</ng-template>
```
Here, we have defined a variable books that is only accessible by our template, we no longer have direct access to the `booklist` in our `.ts`. Where does the `let-books` get its value from?

1. The data for the let-books can be gotten directly from the parent
2. From a default value set in the `*ngTemplateOutlet` `context` property

1. To get the value from the parent 

```
 <app-books-view [booklist]='booklist'></app-books-view> 
```
Here, we are are not passing any template to the books views, this means that we want to use the default template while we are sending our own data to the template. Now, what happens is that

1. Our `books-views` will first check if booklist has been passed as an input value.
2. If the value is passed, it then assign the value of the `booklist` to `let-books` through the context
3. If there is no `booklist` passed to it, it then assign the data specified in the context to the `let-books`. `context:{$implicit:booklist}` which is the default value.

This way, we can successfully send in our own data to `books-view` while still using the default template.

Same way, if we want to send in our custom template and also allow for dynamic data, we can also declare the template scoped variable with the `let`

In our parent component

```
<ng-template let-books #cardView >
   <div let *ngFor="let bk of books" class="books-card">
      <h1>{{bk.name}}</h1>
      <p>{{bk.author}}  - {{bk.year}}</p>
   </div>
</ng-template>

<ng-template let-books #tableView >
  <table>
    <tr>
      <th>Title</th>
      <th>Author</th>
      <th>Year</th>
    </tr>
    <tr *ngFor="let bk of books">
      <td>{{bk.name}}</td>
      <td>{{bk.author}}</td>
      <td>{{bk.year}}</td>
    </tr>
  </table>
</ng-template>

<!-- This allows you to use project your custom template and also pass in the data.   -->
<app-books-view [booklist]='booklist'  [booksTemplate]="cardView"></app-books-view>

```
Here, we have also added the `let-books` variable to our custom templates. Whenever we pass in the data to our template, it assigns the value of the data to our context
whose value is then assigned to the `let-books`

```
<app-books-view [booklist]='booklist'  [booksTemplate]="cardView"></app-books-view>

```

You can check out the github rep for the sample code above

https://github.com/valoni01/ng-magical-directives

##Conclusion

you can utilize the magical power of the `*ngTemplateOutlet`, `ng-template`, `ng-content` and `ng-container` to create your own reusable component library while also giving your users the flexibility to customize the components.

Previous: [ng-content](https://dev.to/valoni01/ng-magical-directives-series-ng-content-2gfk)