JCvanDamme
JCvanDamme

Reputation: 671

Angular calls ngAfterContentInit for each element in ngFor

My Angular 11 (Ioinc 5) application has 2 components: A ContainerComponent and a BoxComponent. Both components are completely translucent (template: '<ng-content></ng-content>') The container component needs to keeps track of the number of box components that a third component places into it. My solution fails when the third component uses *ngFor to place the boxes into the container (Option B below)

container.component.ts

import { Component, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
import { BoxComponent } from './box.component';


@Component({
  selector: 'container',
  template: '<ng-content></ng-content>',
  styleUrls: ['container.component.scss'],
})
export class ContainerComponent implements AfterContentInit {
  // track the number of boxes
  private count: number = 0;

  @ContentChildren(BoxComponent)
  private boxes!: QueryList<BoxComponent>;
  
  ngAfterContentInit(): void {
    this.count++;
    console.log(count);  // Result see below
  }
}

A third component places n BoxComponents into a ContainerComponent.

Option A: n boxes hardcoded into template of third component

<container>
    <box>Box 1</box>
    <box>Box 2</box>
    // ...
    <box>Box n</box>
</container>

Option B: n boxes added into the template of the third component using *ngFor directive

<container *ngFor="let box of boxes">
    <box>{{box.label}}</box>
</container>

Difference between option A and option B

When using option A, Angular calls ngAfterContentInit only once and the result is

count = n

When using option B, Angular calls ngAfterContentInit n times and the count is always 1.

count = 1, 1, ..., 1    // n times

I find this behavior very strange because that means that Angular creates a new instance of ContainerComponent for each BoxComponent.

Upvotes: 2

Views: 215

Answers (1)

Lincoln Alves
Lincoln Alves

Reputation: 654

I believe this is the right behaviour. The ngFor directive will iterate through the boxes list, creating one instance of the injected component for each element.

Maybe the behaviour you expect will be achieved if you put ngFor in the box component:

<container>
  <box *ngFor="let box of boxes">{{box.label}}</box>
</container>

Cheers!

Upvotes: 3

Related Questions