Eternal Sunshine
Eternal Sunshine

Reputation: 77

How can I project dynamic filter chips using `ng-template`, `ngTemplateOutlet`, and `ng-content` in Angular?

I'm working on an Angular application where I need to build a reusable filter bar component that displays dynamic filter chips based on user selections. The filter chips need to be projected into the filter bar using ng-template, ngTemplateOutlet, and ng-content. Additionally, I need to emit an event when a chip is closed.

Current Implementation:

I have a parent component (MyComponent) that tracks active filters in an array called activeFilters. Each filter has a label, value, and key. The parent component passes these filters to the FilterBarComponent using content projection.

Here's the structure:

Parent Component (MyComponent):

<app-filter-bar 
  [hasActiveFilters]="activeFilters.length > 0" 
  (clearAll)="clearAllFilters()">
  <!-- Project filter chips using ng-template and ngTemplateOutlet -->
  <ng-container *ngTemplateOutlet="filterChipTemplate; context: { $implicit: activeFilters, close: onChipClose.bind(this) }"></ng-container>
</app-filter-bar>

<!-- Template for the filter chips -->
<ng-template #filterChipTemplate let-filters let-close="close">
  <ng-container *ngFor="let filter of filters">
    <app-filter-chip 
      [label]="filter.label" 
      [value]="filter.value" 
      [filterKey]="filter.key" 
      (close)="close(filter.key)">
    </app-filter-chip>
  </ng-container>
</ng-template>

FilterBarComponent:

<div class="filter-bar">
  <!-- Clear All Filters link -->
  <a href="#" 
     class="clear-all-filters" 
     (click)="clearAllFilters()" 
     [class.disabled]="!hasActiveFilters">
    Clear All Filters
  </a>

  <!-- Projected Filter Chips -->
  <div class="filter-chips-container">
    <ng-content></ng-content>
  </div>
</div>

FilterChipComponent:

@Component({
  selector: 'app-filter-chip',
  template: `
    <div class="chip">
      <span>{{ label }}: {{ value }}</span>
      <button (click)="close.emit(filterKey)">x</button>
    </div>
  `,
  styleUrls: ['./filter-chip.component.css']
})
export class FilterChipComponent {
  @Input() label: string;
  @Input() value: string;
  @Input() filterKey: string;
  @Output() close = new EventEmitter<string>();
}

Problem:

I’m using a ng-template with ngTemplateOutlet in the parent component to generate the filter chips dynamically. The chips are projected into the FilterBarComponent via ng-content. However, I'm concerned about how this setup will render multiple filter chips (e.g., 4 filters) since I only have a single ng-content in the child component.

Question:

Example Scenario:

Assume I have the following activeFilters array in my parent component:

activeFilters = [
  { label: 'Category', value: 'Books', key: 'category' },
  { label: 'Author', value: 'John Doe', key: 'author' },
  { label: 'Price', value: '$10-$20', key: 'price' },
  { label: 'Rating', value: '4+ stars', key: 'rating' }
];

Thank you in advance for your help!

Upvotes: 0

Views: 83

Answers (1)

BizzyBob
BizzyBob

Reputation: 14750

Your approach of using content projection with ng-template, ngTemplateOutlet, and ng-content valid but overly complicated.

You can simply use the <app-filter-chip> directly in your template:

<app-filter-bar 
  [hasActiveFilters]="activeFilters.length > 0" 
  (clearAll)="clearAllFilters()">

    <app-filter-chip *ngFor="let filter of filters"
      [label]="filter.label" 
      [value]="filter.value" 
      [filterKey]="filter.key" 
      (close)="close(filter.key)"
    />
</app-filter-bar>

This will accomplish the same result, with simpler template.

How does Angular handle projecting multiple components when only one ng-content is used?

Angular's ng-content will project all content from the parent component into the child component at the location of the ng-content tag. This means that even if you have multiple filter chips, they will all be projected into the single ng-content in your FilterBarComponent. You don't need separate ng-content tags for each chip; in fact, Angular only allows a single <ng-content> element (without a selector) per template.

Upvotes: 1

Related Questions