Content Projection Fallback in ng-content in Angular
Tags: angular, tutorial
Published: 2024-07-02 13:38:46

##Introduction

In this blog post, I want to describe a new Angular 18 feature called content projection fallback in ng-content. When content exists between the ng-content opening and closing tags, it becomes the fallback value. When projection does not occur, the fallback value is displayed.

###Bootstrap Application

```typescript
// app.config.ts

export const appConfig: ApplicationConfig = {
  providers: [
    provideExperimentalZonelessChangeDetection()
  ]
};
```

```typescript
// main.ts

import { appConfig } from './app.config';

bootstrapApplication(App, appConfig);
```

Bootstrap the component and the application configuration to start the Angular application.

###Create a Card Component

The `AppCard` component displays the default tier and its default features. Then, an `AppPricingListComponent` encapsulates the `AppCardComponent` and passes the custom tier and features to it to display. Finally, the App component consists of `AppCardComponent` and `AppPricingListComponent` to build the full pricing page.

```typescript
// app-card.component.ts

import { ChangeDetectionStrategy, Component } from "@angular/core";

@Component({
  selector: 'app-card',
  standalone: true,
  template: `
    <div class="header">
      <ng-content select="[header]">Free Tier</ng-content>
    </div>
    <div class="content">
      <ng-content> 
        <ul>
          <li>Free of Charge</li>
          <li>1 License</li>
          <li>500MBs Storage</li>
          <li>No Support</li>
        </ul> 
      </ng-content>
    </div>
    <div class="footer">
      <ng-content select="[footer]">
        <button>Upgrade</button>
      </ng-content>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppCardComponent {}
```

The template of the `AppCardComponent` has three sections: header, content, and footer. The header section consists of a `ng-content` that projects to an element with a header attribute. When the projection does not occur, the fallback value, "Free Tier", is displayed. The content section comprises a `ng-content` element that projects to the default element. This section renders the free tier's features when the projection does not occur. The footer section comprises a `ng-content` element that projects to an element with a footer attribute. When the projection does not occur, the upgrade button is rendered.

###Create a Price Listing Component

This is a simple component that encapsulates `AppCardComponent` to display the signal input values of a tier and its features.

```typescript
// app-price-list.component.ts

import { ChangeDetectionStrategy, Component, input } from "@angular/core";
import { AppCardComponent } from "./app-card.component";

@Component({
  selector: 'app-price-card',
  standalone: true,
  imports: [AppCardComponent],
  template: `
    <section>
      <h2>Custom Content</h2>
      <app-card>
        <div header>{{ tier() }}</div>
        <ul>
          @for (item of features(); track item) {
            <li>{{ item }}</li>
          }
        </ul>
      </app-card>
    </section>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppPricingListComponent {
  tier = input<string>('');
  features = input<string[]>([]);
}
```

###Build a full pricing page using content projection fallback

```typescript
// main.ts

import { Component, VERSION } from '@angular/core';
import { AppCardComponent } from './app-card.component';
import { AppPricingListComponent } from './app-price-list.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [AppCardComponent, AppPricingListComponent],
  template: `
    <header>Angular {{version}} - Content Projection fallback </header>

    <main>
      <section>
        <h2>Fallback content</h2>
        <app-card />
      </section>

      <app-price-list tier="Start-up" [features]="startUpFeatures" />
      <app-price-list tier="Company" [features]="companyFeatures" />
      <section>
        <h2>Custom Content</h2>
        <app-card>
          <div header>Enterprise</div>
          <ul>
            <li>Contact sales for quotation</li>
            <li>200+ Licenses</li>
            <li>1TB Storage</li>
            <li>Email and Phone Technical Support</li>
            <li>99.99% Uptime</li>
          </ul>
          <div footer>&nbsp;</div>
        </app-card>
      </section>
    </main>
   `,
})
export class App {
  version = VERSION.full;

  startUpFeatures = [
    'USD 10/month',
    '3 Licenses',
    '1GB Storage',
    'Email Technical Support',
  ];

  companyFeatures = [
    'USD 100/month',
    '50 Licenses',
    '20GB Storage',
    'Email and Phone Technical Support',
    '95% Uptime'
  ];
}
```

The pricing page has four cards: the free tier card displays the fallback values because the `AppCardComponent` does not have a body. The startup and company cards use the footer's fallback value and project the header and features.  Therefore, both cards still display the upgrade button. Similarly, the enterprise card projects the header and features. It also projects the footer to replace the upgrade button with a blank row.

The following Stackblitz repo displays the final results:

{%embed https://stackblitz.com/edit/angular-content-projection-fallback-qh5hcd?file=src%2Fmain.ts %}

This is the end of the blog post that describes content projection fallback with ng-content in Angular 18. I hope you like the content and continue to follow my learning experience in Angular, NestJS, GenerativeAI, and other technologies.

##Resources:

- Stackblitz Demo: https://stackblitz.com/edit/angular-content-projection-fallback-qh5hcd?file=src%2Fmain.ts
- Github Repo: https://github.com/railsstudent/ng-content-projection-default-demo
- Github Page: https://railsstudent.github.io/ng-content-projection-default-demo/