velediqa
velediqa

Reputation: 53

how to count *ngif inside *ngfor

I want to know how many items are displayed in following piece of code.

component.html:

<p *ngIf="counter">{{counter}} items displayed!</p>
<p *ngIf="!counter">Nothing to display!</p>

<ng-container *ngFor="let item of items">
  <li *ngIf="item.count < 100">{{counter}}. {{item.title}}</li>
</ng-container>

component.ts:

export class ListComponent {
  @Input('items') items: any[];

  constructor() { }

  protected counter: number = 0;
}

items (json presentation):

[
    {id: 1, count: 100, title: "title #1"},
    {id: 2, count: 20, title: "title #2"},
    {id: 3, count: 0, title: "title #3"},
    {id: 4, count: 200, title: "title #4"},
    {id: 5, count: 100, title: "title #5"},
]

Note 1: In above sample data, only count property of objects in the array can change at any moment of time, by any other component of the app.

Note 2: Actually, items is an array of arrays, but for better representation and better understanding I changed it to array of objects here.


I tried to count HTML nodes but this error happens:

NG0100: ExpressionChangedAfterItHasBeenCheckedError

I also tried *ngIf="addCounter(item.count < 100)" but addCounter() is triggered on every event on page (scroll events, etc).

I also can not filter items in the ts file (there are lots of ngfors and the items is constantly updated, so the ts file gets too complicated because of just a simple counter).

Is there a better approach out there?

Upvotes: 2

Views: 2811

Answers (3)

Totati
Totati

Reputation: 1592

I'd do something like this: Create the interface for the JSON

export interface ListItem {
  id: number;
  count: number;
  title: string;
}

And use it in your component.ts:

@Component({selector: 'app-list', changeDetection: ChangeDetection.OnPush})
export class ListComponent {
  @Input() items?: ListItem [] | null; // with ? and | null your component will work with async pipe and strict TypeScript
}

Your ListComponent html:

<p *ngIf="items?.length">{{items.length}} items displayed!</p>
<p *ngIf="!items?.length">Nothing to display!</p>

<ol> // It's always better to use native html element.
  <li *ngFor="let item of items">{{item.title}}</li>
</ol>

Create an Angular pipe that does all the logic:

@Pipe({name: 'countFilter'})
export class CountFilterPipe implements PipeTransform{
  transform(value: ListItem[] | null | undefined) { // with the above logic mark null | undefined too.
    if(!value) {
      return;
    }
    return value.filter(item => item => item.count < 100);
  }
}

Your AppComponent or any component html where you use app-list:

<app-list [items]="items | countFilter"></app-list>

Upvotes: 0

Apoorva Chikara
Apoorva Chikara

Reputation: 8773

As per the comments, you can add a counter in the TS file add a method to increment every time item.counter < 100;

Updated Answer based on the comment and extra information provided in the question.

You can use set property on input. Create two variables: counter(already there) and itemList in the ts file :

export class ListComponent {
  protected counter: number = 0;
  public itemList: Array<any>= [];
  @Input('items') set item (value) {
      console.log(value) // now you get items here
      this.itemList = value;
      this.filterBasedOnCount(value);
  }

  constructor() { }
  
  filterBasedOnCount(items) {
     this.counter =  items.filter(item => item.count < 100).length;
  }
  
}

Note 2: Actually, items is an array of arrays, but for better representation and better understanding I changed it to array of objects here.

You can use flatMap in the filter to get only the list of object just for counting purpose.

Upvotes: 0

Rami Assi
Rami Assi

Reputation: 1129

You should do all data filtering, processing, and preparation in the component class. The template should be used for displaying data only. This is some approach to display the items according to the conditions mentioned in the question:

component.ts

filteredItems = items.filter(item => item.count < 100);
counter = filteredItems.length;

template.html

<p *ngIf="counter">{{counter}} items displayed!</p>
<p *ngIf="!counter">Nothing to display!</p>

<ng-container *ngFor="let item of filteredItems; let i = index">
  <li>{{i}}. {{item.title}}</li>
</ng-container>

Upvotes: 1

Related Questions