Understanding Content Projection
Published: 2021-10-13 04:29:32

## What is Content Projection?

Content projection helps us with a way to build reusable components. It provides us with the ability to insert or project the content we want to use inside another component.

There are three common implementations of content projection in Angular :

1. Single-slot content projection
2. Multi-slot content projection
3. Conditional content projection

In this article, we will go through each of the common implementations. 

Let us start by creating a new Angular application.

{% link https://dev.to/queencykoh/setting-up-angular-project-4nnd %}

We are going to build an Authentication Form

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3rs7e90dgpcmtpb9u6ua.png)

I used TailwindCSS to rapidly build modern looking UI.

{% link https://dev.to/queencykoh/setting-up-tailwind-css-in-angular-11b4 %}
 
We will not talk about TailwindCSS in this article. But if you like to have the same UI visit the GitHub repository👇🏼 and copy the styles in each CSS file. 

{% github queency-koh/content-projection no-readme %}

Next, create an Auth feature module

{% link https://dev.to/queencykoh/creating-a-feature-module-458 %}

Finally, create a Auth Form component using Angular CLI

```
ng g c auth/AuthForm
```

Modify `auth-form.component.html` and add the following lines of code.

```html
<h2>Sign in to your account</h2>
<p>Dont have an account?
    <a routerLink="/auth/register">Sign up</a>
</p>

<div>
  <div>
    <div>
      <label for="email">Email address</label>
      <input id="email" name="email" type="email"/>
    </div>

    <div>
      <label for="password">Password</label>
      <input id="password" name="password" type="password"/>
    </div>

    <button type="submit">Sign in</button>
  </div>

<div>
  <div>
    <span>or</span>
  </div>

  <div>
    <div>
      <a href="#">
        <i class="lab la-facebook text-lg"></i>
      </a>
      <a href="#">
        <i class="lab la-google text-lg"></i>
      </a>
      <a href="#">
        <i class="lab la-github text-lg"></i>
      </a>
    </div>
  </div>
</div>
```

Let us now create a new login form component

`ng g c auth/login`

Modify `login.component.html` and add the following lines of code.

```html
<app-auth-form></app-auth-form>
```

Run your Angular application using `ng serve`

Now that we have created our login form, we will now create a registration form. The registration form will also have email and password input and a submit button. But it will have a different header, a different link, and a different text for the submit button. 

Let's create a new component name `register`

```
ng g c auth/register
```

Modify `register.component.html` and add the following lines of code.

```html
<app-auth-form></app-auth-form>
```

Go back to your browser  and navigate to `auth/register`. As expected, we will have the same UI as the login form. In the next section, we will use Single-slot content projection to customize each component.

## Making Reusable Component using Single-slot content projection

Single-slot content projection is the most basic form of content projection. With this, a component accepts content from a **single** source using `<ng-content></ng-content>`

On your `register.component.html`. Insert `<h2>Register your account</h2>` in the <app-auth-form></app-auth-form>

```html
<app-auth-form>
  <h2>Register your account</h2>
</app-auth-form>
```

To project the content we need to replace `<h2 class="auth-header">Sign in to your account</h2>` in the `auth-form.component.html` with `<ng-content></ng-content>`.

```html
<ng-content></ng-content>

<p>Don't have an account?
    <a routerLink="/auth/register">Sign up</a>
</p>

<!-- Code below omitted for clarity -->
```

This will break the `login.component.html` so go ahead and insert `<h2>Sign in to your account</h2>` in the <app-auth-form></app-auth-form> in your `login.component.html` file.

```html
<app-auth-form>
  <h2>Sign in to your account</h2>
</app-auth-form>
```

Go back to your browser and navigate to `auth/register`.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a4220hmqzk0vzsjynflo.png)
 
The header was changed successfully. However, we also have to change the link and button text. 

```html
<app-auth-form>
  <h2>Register your account</h2>
  <p>
    <a routerLink="/auth/login"> Already have an account?</a>
  </p>
  <button type="submit">Sign Up</button>
</app-auth-form>

```

Modify `auth-form.component.html`

```html
<ng-content></ng-content>
<ng-content></ng-content>

<div class="auth-form-container">
  <div class="auth-form">
    <div>
      <label for="email">Email address</label>
      <input id="email" name="email" type="email" />
    </div>

    <div>
      <label for="password">Password</label>
      <input id="password" name="password" type="password" />
    </div>
    <ng-content></ng-content>
  </div>
  <!-- Code below omitted for clarity -->
</div>
```

However, adding multiple `<ng-content></ng-content>` will only project content from a single source.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p81zi1vbb981g8fij4zu.png)

In the next section, we will look into Multi-slot content projection to help us project content from multiple sources.
 
## Multi-slot content projection to the rescue

With the multi-slot content projection pattern, a component accepts content from multiple sources. To determine which content goes into which slot, we can use the `select` attribute of `<ng-content></ng-content>`.

Go back to the `auth-form` template and on the `<ng-content><ng-content>` add the `select` attribute and specify a CSS selector for each slot where you want the content to be projected.

Angular supports selectors like tag name, attribute, CSS class, and the `:not` pseudo-class. In this example we are going to use only the tag name.

```html
<ng-content select="h2"></ng-content>
<ng-content select="p"></ng-content>

<div class="auth-form-container">
  <div class="auth-form">
    <div>
      <label for="email">Email address</label>
      <input id="email" name="email" type="email" />
    </div>

    <div>
      <label for="password">Password</label>
      <input id="password" name="password" type="password" />
    </div>
    <ng-content select="button"></ng-content>
  </div>
  <!-- Code below omitted for clarity -->
</div>

```

Go back to your browser and as you will see the projected contents now appear on the specified location.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nfw2guvekm9xl3pb4ovy.png)

## Reuse HTML blocks with conditional content projection

Conditional Content Projection is great when creating reusable HTML blocks. It renders the content when specific conditions are met. There are advanced cases for Conditional content projection but we will only discuss its most basic form.

Create a new Social Login component

`ng new g c auth/SocialLogin`

Using `<ng-template>` to hold template content that will be used by `ngTemplateOutlet` [Structural directives](https://angular.io/guide/structural-directives).

Modify `social-login.component.html`

```html
<ng-template #socialLogin></ng-template>
```

Using `ngTemplateOutlet` to instantiate the template using its template reference, `#socialLogin`

```html
<ng-container *ngTemplateOutlet="socialLogin"></ng-container>
```

Using `context` object we can associate `ngTemplateOutlet` variables with <ng-template> elements that can be accessed from within the template using `let`.

```html
<ng-template #socialLogin let-icon="icon">
    {{ icon }}
</ng-template>

<ng-container *ngTemplateOutlet="socialLogin; context: { icon: 'lab la-facebook-f'}"></ng-container>
```

We can also instantiate the template multiple times

```html
    <ng-container
      *ngTemplateOutlet="socialLogin; context: { icon: 'lab la-facebook-f' }"
    ></ng-container>
    <ng-container
      *ngTemplateOutlet="socialLogin; context: { icon: 'lab la-google' }"
    ></ng-container>
    <ng-container
      *ngTemplateOutlet="socialLogin; context: { icon: 'lab la-github' }"
    ></ng-container>
```

The final code should look like this :

```html
  <div class="social-login-grid">
    <ng-template #socialLogin let-icon="icon">
      <a href="#">
        <i class="{{ icon }} text-lg"></i>
      </a>
    </ng-template>

    <ng-container
      *ngTemplateOutlet="socialLogin; context: { icon: 'lab la-facebook-f' }"
    ></ng-container>
    <ng-container
      *ngTemplateOutlet="socialLogin; context: { icon: 'lab la-google' }"
    ></ng-container>
    <ng-container
      *ngTemplateOutlet="socialLogin; context: { icon: 'lab la-github' }"
    ></ng-container>
  </div>
```

Go back to your browser and see the final result

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3rs7e90dgpcmtpb9u6ua.png)