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 :
- Single-slot content projection
- Multi-slot content projection
- 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
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
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>
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>
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.
<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>
<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>
.
<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 in your login.component.html
file.
<app-auth-form>
<h2>Sign in to your account</h2>
</app-auth-form>
Go back to your browser and navigate to auth/register
.
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>
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>
However, adding multiple <ng-content></ng-content>
will only project content from a single source.
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>
Go back to your browser and as you will see the projected contents now appear on the specified location.
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>
Using ngTemplateOutlet
to instantiate the template using its template reference, #socialLogin
<ng-container *ngTemplateOutlet="socialLogin"></ng-container>
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>
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>
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>
Go back to your browser and see the final result
Top comments (0)