1

I have a nested loop in a table, but i want to only show a limited amount of rows in the DOM and load more when a button is clicked.

The Loop looks something like this:

<ng-container *ngFor="let organisation of organisations; let organisationIndex = index;">
    <ng-container *ngFor="let department of organisation.departments; let departmentIndex = index;">
        <ng-container *ngFor="let user of department.users; let userIndex = index;">
            <div>
                This is a List entry and i only want this 20 times in total (not for each loop)
            </div>
        </ng-container>
    </ng-container>
</ng-container>

What would be the smoothest way to get the index inside the nested loop, if possible without adding complexity?

I do not want to do it with css :nth-of-type or something similiar since my dataset is huge and the dom gets slow even if elements are hidden with css.

The length of each arrays is dynamic which prevents me from making a static formula (like organisationIndex * 50).

Jeremias Nater
  • 650
  • 3
  • 18

1 Answers1

3

There are multiple options how you can deal with this one:

Server pre-processing Trim the data on the server and send to client only set you would like to render. The advantage is the client's browser does not have to deal with large amount of data (and download them). Or introduce a "totalIndex" on each user that is incremental and continuous over organisations.

Angular trimming - equal number of items in sub-collections

If your items in your sub-collections contain the same number of items. You have to multiply the indexes and check whether you already output 20 rows. If so use *ngIf to avoid showing more of them. When using *ngIf item is not rendered at all.

<ng-container *ngFor="let organisation of organisations; let organisationIndex = index;">
    <ng-container *ngFor="let department of organisation.departments; let departmentIndex = index;">
        <ng-container *ngFor="let user of department.users; let userIndex = index;">
            <div *ngIf="(organisationIndex * organisation.departments.length) + (departmentIndex * department.users.length) + userIndex < 20">
                This is a List entry and i only want this 20 times
            </div>
        </ng-container>
    </ng-container>
</ng-container>

Angular trimming - counterpipe If number of items in sub-collections is not equal use counterpipe.

import { Pipe, PipeTransform } from '@angular/core';
import { Counter } from './counter';

const counters = new WeakMap<any, Counter>();

@Pipe({
  name: 'counterPipe'
})
export class CounterPipe implements PipeTransform  {
  transform(value: any): Counter {
    if (!counters.has(value)) {
      counters.set(value, new Counter());
    }
    return counters.get(value);
  }
}

This will add counter while processing entities in *ngFor so you always know the number of element you are rendering.

Wrap your code with this pipe:

<ng-container *ngFor="let counter of [organisations | counterPipe]">
  <ng-container *ngFor="let organisation of organisations; let organisationIndex = index;">
      <ng-container *ngFor="let department of organisation.departments; let departmentIndex = index;">
          <ng-container *ngFor="let user of department.users; let userIndex = index;">
              <div *ngIf="counter.inc() < 20">
                This is a List entry and i only want this 20 times
              </div>
          </ng-container>
      </ng-container>
  </ng-container>
</ng-container>

Example of usage can be seen on https://stackblitz.com/edit/angular-nested-ngfor-ydvxjv

michal.jakubeczy
  • 8,221
  • 1
  • 59
  • 63
  • But wouldnt this slice just limit the most inner Array? i would like to have 20 entries in total not per department. Ill adjust my question to state that i want 20 entrie sin total. – Jeremias Nater Aug 04 '21 at 10:04
  • so move slice on the top `*ngFor` ... I have adjusted my answer. – michal.jakubeczy Aug 04 '21 at 10:09
  • This will not show 20 entries if you have 20 organisations with 10 departments each and 10 users pre department (just as an example of content of the arrays. No matter which *ngfor you limit you will not be able to slice the end result of entries – Jeremias Nater Aug 04 '21 at 16:26
  • @JeremiasNater now I understand what you mean. You just need 20 records no matter what the level is. Let me change the answer for you. – michal.jakubeczy Aug 05 '21 at 06:56
  • @JeremiasNater I have changed the answer according to your explanation. – michal.jakubeczy Aug 05 '21 at 07:03
  • Great answer and thanks for your effort! Sadly in my case the array sizes are dynamic, which means if you are at index 2 of the organisations array you would need to calculate how many users are in organisation 1 and add that to the calculation you suggested, and the complexity of that calculation would grow as you go further in the index. What i ended up doing was pre-processing my data so that i have a "totalIndex" on each user that is incremental and continuous over ofganisations. – Jeremias Nater Aug 05 '21 at 12:08
  • @JeremiasNater glad to hear you found a solution. Server pre-processing is somthing I also mentioned. You can even trim your data on server and avoid client processing. Anyway, for such case you can also use CounterPipe - see solution https://stackblitz.com/edit/angular-nested-ngfor-ydvxjv?file=app%2Fapp.component.html – michal.jakubeczy Aug 05 '21 at 15:34
  • Thats awesome, I will definetely use the counter pipe. I approved your answer but i suggest you add the CounterPipe to the answer for helpfulness because its exactly hat i was looking for. – Jeremias Nater Aug 06 '21 at 09:24
  • @JeremiasNater thanks for approval, I modified the answer showing all approaches we discussed here. This gives further reader ability to choose one which fits best for the case. – michal.jakubeczy Aug 06 '21 at 10:22