Using ng-content for Content Projection
Tags: angular, frontend
Published: 2019-08-31 01:45:07

Content projection is a useful technique in Angular to modify component content. Let's learn it from a very simple scenario. 

You have a button component `button.component.ts` using `<button>` and currently, the text used there is `submit`

`button.component.ts`
```ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-button',
  template: `
    <button class="primary-button">
      submit
    </button>
  `,
  styles: [
    '.primary-button { background: #94FEB1; padding: 10px; }'
  ]
})
export class AppButtonComponent  {  
}

```

Then we use that component twice in the HTML template of `app component`.

`app.component.html`
```html
<app-button></app-button>
<app-button></app-button>
```

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/gpl8hce31y7gzizqmdnb.png)

It will definitely show two buttons with `submit` text.  

How can we customize the text for that component? Let's say I want to display `login` text for the first button and `register` for the second one. 

One approach is just to use `@Input` so, the text can be passed as property as such

`app.component.html`
```html
<app-button [text]="login"></app-button>
<app-button [text]="register"></app-button>
```

That's not too bad but we can try another approach using `ng-content`. To use it, we put `<ng-content></ng-content>` as replacement of `submit` text. 


`button.component.ts`
```html
<button class="primary-button">
  <ng-content></ng-content>
</button>
```

Then in the HTML template

`app.component.html`
```html
<app-button>login</app-button>
<app-button>register</app-button>
``` 

Woah, this looks familiar like `<button>` HTML tag. See that the inner content of `<app-button>` are projected into the `<ng-content></ng-content>` that we define in the HTML template of button component. 

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/jk4okw4m53uwhctka4rr.png)

That is a single content projection. Interestingly, we can also use `ng-content` for multi-content projection. 

# Multi-content Projection

Let's say we have a new component named **Banner**. It looks like this

```ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-banner',
  template: `
    <div class="banner">
      <div class="banner-title">
        title
      </div>
      <div class="banner-description">
        description        
      </div>
    </div>
  `,
  styles: [
    '.banner { background: #C1FDFE; padding: 10px; border: 1px solid #333 }',
    '.banner-title { font-weight: bold; font-size: 20px; color: #5D83FE }',    
  ]
})
export class AppBannerComponent  {  
}
```

`app.component.html`

```html
<app-banner></app-banner>

```

Here is when it is displayed

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/tbh31xa1azjmb11p1jo8.png)

Notice that we have a blue color for `title` and black color for `description`. What we're trying to do is to use `ng-content` to modify `title` and `description` in the banner component. 

You must think that it should be easy to solve by adding it like below

`banner.component.ts`

```html
<div class="banner">
  <div class="banner-title">
    <ng-content></ng-content>
  </div>

  <div class="banner-description">
    <ng-content></ng-content>
  </div>
</div>

```
Then

`app.component.html`
```html
<app-banner> 
  <h1>Black Friday Discount</h1>
  <p>50% discount for specific products</p>
</app-banner>
```
And this what happened

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/vmxcqlol2u25ubxr8l5z.png)

Can you spot something wrong? Yes, our banner title is not shown in blue color. Apparently, if we have multiple `ng-content` specified in the template, Angular will only use the latest defined one. In our case, it will only replace `ng-content` in `banner-description`.We should project title and description to correct place. 

To solve this, `ng-content` has a property named `select`. If you specify `select` with a CSS matching selector, it will get only the elements matching the selector from the passed in content.

```html
<div class="banner">
  <div class="banner-title">
    <ng-content select="h1"></ng-content>
  </div>
  <div class="banner-description">
    <ng-content select="p"></ng-content>       
  </div>
</div>
```
for `banner-title`, we target `h1` tag, and for `banner-description`, we're looking for `p` tag from the passed content. 

And the result will be

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/2cfd6e952f9ebof3lhcv.png)

This is what we want. Awesome! 

Remember that `select` property accepts CSS selector, so you can also use class or any valid CSS selector, for example: 

```html
<ng-content select=".my-title"></ng-content>
```

[Code example on Stackblitz](https://stackblitz.com/edit/20190831-ng-content)

# Summary
`ng-content` is a way to pass content into a component. It has `select` property that allows us to have multiple content projection. 

Hope this article is useful!