DEV Community

queencykoh
queencykoh

Posted on • Updated on

Understanding Content Projection

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.

We are going to build an Authentication Form

image

I used TailwindCSS to rapidly build modern looking UI.

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.

Next, create an Auth feature module

Finally, create a Auth Form component using Angular CLI

ng g c auth/AuthForm
Enter fullscreen mode Exit fullscreen mode

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

<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>
Enter fullscreen mode Exit fullscreen mode

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.

<app-auth-form></app-auth-form>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

<app-auth-form></app-auth-form>
Enter fullscreen mode Exit fullscreen mode

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>
  <h2>Register your account</h2>
</app-auth-form>
Enter fullscreen mode Exit fullscreen mode

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>.

<ng-content></ng-content>

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

<!-- Code below omitted for clarity -->
Enter fullscreen mode Exit fullscreen mode

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

<app-auth-form>
  <h2>Sign in to your account</h2>
</app-auth-form>
Enter fullscreen mode Exit fullscreen mode

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

image

The header was changed successfully. However, we also have to change the link and button text.

<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>

Enter fullscreen mode Exit fullscreen mode

Modify auth-form.component.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>
Enter fullscreen mode Exit fullscreen mode

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

image

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.

<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>

Enter fullscreen mode Exit fullscreen mode

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

image

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.

Modify social-login.component.html

<ng-template #socialLogin></ng-template>
Enter fullscreen mode Exit fullscreen mode

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

<ng-container *ngTemplateOutlet="socialLogin"></ng-container>
Enter fullscreen mode Exit fullscreen mode

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

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

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

We can also instantiate the template multiple times

    <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>
Enter fullscreen mode Exit fullscreen mode

The final code should look like this :

  <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>
Enter fullscreen mode Exit fullscreen mode

Go back to your browser and see the final result

image

Top comments (0)