Reputation: 53
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
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
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
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