Advanced Component Templates in Angular

Angular’s component-based architecture allows developers to build highly reusable, flexible, and dynamic UI components. While basic templates are straightforward, advanced Angular applications require techniques such as nested components, content projection, dynamic templates, and directive combinations to create scalable and maintainable layouts.

This post explores advanced component template techniques in Angular, including nested components with content projection, dynamic templates using ngTemplateOutlet, and combining structural and attribute directives.

Table of Contents

  1. Introduction to Advanced Templates
  2. Nested Components
    1. Basics of Component Nesting
    2. Passing Data Between Components
  3. Content Projection with <ng-content>
    1. Single Slot Content Projection
    2. Multi-slot Content Projection
    3. Conditional Projection
  4. Dynamic Templates with ngTemplateOutlet
    1. Using Template References
    2. Passing Context to Templates
    3. Switching Templates Dynamically
  5. Combining Structural and Attribute Directives
    1. Flexible Layouts with *ngIf and ngClass
    2. Conditional Styling and Rendering
  6. Practical Examples
    1. Nested Card Component with Header and Body Projection
    2. Dynamic Dashboard with Template Outlets
    3. Flexible List with Conditional Styles and Rendering
  7. Best Practices
  8. Common Pitfalls
  9. Conclusion

1. Introduction to Advanced Templates

Angular templates are declarative views tied to component classes. For advanced applications, we often need:

  • Nested components to modularize UI
  • Content projection to allow customizable layouts
  • Dynamic templates for reusable patterns
  • Directive combinations for flexible layouts

These techniques make components reusable, maintainable, and adaptable to various UI needs.


2. Nested Components

2.1 Basics of Component Nesting

Nested components are components embedded inside other components. This allows UI modularization and separation of concerns.

Example:

// card.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-card',
  template: `
&lt;div class="card"&gt;
  &lt;div class="card-header"&gt;{{ title }}&lt;/div&gt;
  &lt;div class="card-body"&gt;
    &lt;ng-content&gt;&lt;/ng-content&gt;
  &lt;/div&gt;
&lt;/div&gt;
`, styles: [`
.card { border: 1px solid #ccc; padding: 10px; border-radius: 4px; }
.card-header { font-weight: bold; margin-bottom: 10px; }
`] }) export class CardComponent { @Input() title!: string; }

Usage in a parent component:

@Component({
  selector: 'app-dashboard',
  template: `
&lt;app-card title="User Info"&gt;
  &lt;p&gt;Name: John Doe&lt;/p&gt;
  &lt;p&gt;Age: 30&lt;/p&gt;
&lt;/app-card&gt;
&lt;app-card title="Settings"&gt;
  &lt;p&gt;Theme: Dark&lt;/p&gt;
  &lt;p&gt;Notifications: Enabled&lt;/p&gt;
&lt;/app-card&gt;
` }) export class DashboardComponent {}
  • Each app-card is nested inside the parent, and content is projected into ng-content.

2.2 Passing Data Between Components

Data can flow from parent to child using @Input() and from child to parent using @Output().

Example with nested components:

@Component({
  selector: 'app-child',
  template: &lt;button (click)="notifyParent()"&gt;Click Me&lt;/button&gt;
})
export class ChildComponent {
  @Output() clicked = new EventEmitter<void>();

  notifyParent() {
this.clicked.emit();
} } @Component({ selector: 'app-parent', template: &lt;app-child (clicked)="onChildClicked()"&gt;&lt;/app-child&gt; }) export class ParentComponent { onChildClicked() {
console.log('Child component clicked');
} }
  • This pattern is often combined with nested templates to create interactive UI components.

3. Content Projection with <ng-content>

Content projection allows a component to receive external content from its parent.

3.1 Single Slot Content Projection

Basic usage:

@Component({
  selector: 'app-panel',
  template: `
&lt;div class="panel"&gt;
  &lt;ng-content&gt;&lt;/ng-content&gt;
&lt;/div&gt;
` }) export class PanelComponent {}

Usage:

<app-panel>
  <p>This content is projected into the panel</p>
</app-panel>
  • The paragraph is inserted into the <ng-content> slot.

3.2 Multi-slot Content Projection

Angular supports named slots using select attribute:

@Component({
  selector: 'app-layout',
  template: `
&lt;header&gt;&lt;ng-content select="&#91;header]"&gt;&lt;/ng-content&gt;&lt;/header&gt;
&lt;main&gt;&lt;ng-content select="&#91;body]"&gt;&lt;/ng-content&gt;&lt;/main&gt;
&lt;footer&gt;&lt;ng-content select="&#91;footer]"&gt;&lt;/ng-content&gt;&lt;/footer&gt;
` }) export class LayoutComponent {}

Usage:

<app-layout>
  <div header>Header Content</div>
  <div body>Main Body Content</div>
  <div footer>Footer Content</div>
</app-layout>
  • Each content element is projected into the correct slot based on the select attribute.

3.3 Conditional Projection

Content projection can be combined with *ngIf for conditional content:

<app-layout>
  <div header *ngIf="showHeader">Header Content</div>
  <div body>Main Content</div>
  <div footer *ngIf="showFooter">Footer Content</div>
</app-layout>
  • This allows dynamic layouts based on component state.

4. Dynamic Templates with ngTemplateOutlet

ngTemplateOutlet allows rendering templates dynamically and is useful for reusable UI patterns.


4.1 Using Template References

Define a template with ng-template:

<ng-template #greetingTemplate let-name="name">
  <p>Hello, {{ name }}!</p>
</ng-template>

Render dynamically:

<ng-container *ngTemplateOutlet="greetingTemplate; context: { name: 'John' }"></ng-container>
  • let-name="name" allows passing data from context.

4.2 Passing Context to Templates

Templates can receive multiple variables:

<ng-template #userTemplate let-user="user" let-index="index">
  <p>{{ index + 1 }}: {{ user.name }}</p>
</ng-template>

<ng-container *ngTemplateOutlet="userTemplate; context: { user: userObj, index: 0 }"></ng-container>
  • This enables dynamic rendering of lists or cards using a single template.

4.3 Switching Templates Dynamically

You can switch templates based on state:

<ng-template #templateA><p>Template A</p></ng-template>
<ng-template #templateB><p>Template B</p></ng-template>

<ng-container *ngTemplateOutlet="currentTemplate"></ng-container>

Component class:

currentTemplate!: TemplateRef<any>;

@ViewChild('templateA') templateA!: TemplateRef<any>;
@ViewChild('templateB') templateB!: TemplateRef<any>;

ngOnInit() {
  this.currentTemplate = this.templateA;
}

switchTemplate() {
  this.currentTemplate = this.currentTemplate === this.templateA ? this.templateB : this.templateA;
}
  • This is useful for tabs, modals, or dynamic sections.

5. Combining Structural and Attribute Directives

Angular directives allow dynamic and conditional rendering of templates. Combining structural and attribute directives enables flexible layouts.


5.1 Flexible Layouts with *ngIf and ngClass

<div *ngIf="isVisible" [ngClass]="{ 'highlight': isHighlighted, 'dim': !isHighlighted }">
  Dynamic Content
</div>
  • *ngIf controls visibility
  • [ngClass] applies conditional styling

5.2 Conditional Styling and Rendering

<div *ngFor="let item of items" [ngStyle]="{ color: item.active ? 'green' : 'gray' }">
  {{ item.name }}
</div>
  • Combines looping (*ngFor) with conditional styles.
  • Enables highly customizable and reusable layouts.

6. Practical Examples

6.1 Nested Card Component with Header and Body Projection

@Component({
  selector: 'app-card',
  template: `
&lt;div class="card"&gt;
  &lt;div class="card-header"&gt;&lt;ng-content select="&#91;header]"&gt;&lt;/ng-content&gt;&lt;/div&gt;
  &lt;div class="card-body"&gt;&lt;ng-content select="&#91;body]"&gt;&lt;/ng-content&gt;&lt;/div&gt;
&lt;/div&gt;
` }) export class CardComponent {}

Usage:

<app-card>
  <div header>User Info</div>
  <div body>
&lt;p&gt;Name: Alice&lt;/p&gt;
&lt;p&gt;Age: 28&lt;/p&gt;
</div> </app-card>

6.2 Dynamic Dashboard with Template Outlets

<ng-template #adminTemplate><p>Admin View</p></ng-template>
<ng-template #userTemplate><p>User View</p></ng-template>

<ng-container *ngTemplateOutlet="currentTemplate"></ng-container>
@ViewChild('adminTemplate') adminTemplate!: TemplateRef<any>;
@ViewChild('userTemplate') userTemplate!: TemplateRef<any>;
currentTemplate!: TemplateRef<any>;

ngOnInit() {
  this.currentTemplate = this.isAdmin ? this.adminTemplate : this.userTemplate;
}

6.3 Flexible List with Conditional Styles and Rendering

@Component({
  selector: 'app-item-list',
  template: `
&lt;div *ngFor="let item of items" 
     &#91;ngClass]="{ 'active': item.active, 'inactive': !item.active }"&gt;
  {{ item.name }}
&lt;/div&gt;
` }) export class ItemListComponent { items = [
{ name: 'Item 1', active: true },
{ name: 'Item 2', active: false },
{ name: 'Item 3', active: true }
]; }
  • Combines structural directives (*ngFor) with attribute directives (ngClass) for a flexible and reusable list layout.

7. Best Practices

  1. Use content projection to create reusable wrapper components.
  2. Use ngTemplateOutlet for dynamic sections or templates.
  3. Combine structural and attribute directives carefully to avoid conflicts.
  4. Modularize nested components for maintainability.
  5. Use context variables to pass data into templates dynamically.
  6. Keep templates clean and readable, separating logic into the component class.

8. Common Pitfalls

  1. Overusing <ng-content> for deeply nested components, making templates hard to maintain.
  2. Forgetting to pass context when using ngTemplateOutlet.
  3. Overlapping structural and attribute directives causing conflicting behavior.
  4. Relying too heavily on dynamic templates for simple static content.
  5. Not using ViewChild to reference templates properly, leading to undefined errors.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *