Nested *ngFors - better alternative? (Angular 7)

Viewed 556

I have an object which contains an array - this array can contain one or more objects of the same type as the parent one. There is no boundary on the amount of levels possible. To display all data in the current data set I have this code:

<div *ngIf="selectedUserMappings">
  <ul *ngFor="let group of selectedUserMappings.childGroups">
    <li style="font-weight:600;">{{group.name}}</li>
    <div *ngFor="let child of group.childGroups">
      <li *ngIf="child.active">{{child.name}}</li>
      <div *ngFor="let n2child of child.childGroups">
        <li *ngIf="n2child.active">{{n2child.name}}</li>
        <div *ngFor="let n3child of n2child.childGroups">
          <li *ngIf="n3child.active">{{n3child.name}}</li>
        </div>
      </div>
    </div>
  </ul>
</div>

Which is not a very good way to achieve the wanted result. Any pointers on a better approach?

Edit: From the suggestions so far, I think the way to go will be a dummy component. I need to show the outer parents with different styling in the list, which I will be able to now. But, I also need to hide top-level parents where no childs are active (all the objects have an active-boolean), as well as childs that are not active. The children of non-active childs should still be visible though. Any ideas? This is what I've got so far:

import { Component, OnInit, Input } from '@angular/core';
import { UserGroupMapping } from 'src/app/models/models';

@Component({
  selector: 'list-item',
  templateUrl: './list-item.component.html',
  styleUrls: ['./list-item.component.scss']
})
export class ListItemComponent implements OnInit {
  @Input() data;
  list: Array<any> = [];

  constructor() { }

  ngOnInit() {
    this.data.forEach(item => {
    this.createList(item, true);
    });
  }

  createList(data, parent?) {
    if (parent) {
       this.list.push({'name': data.name, 'class': 'parent'});
       if (data.childGroups && data.childGroups.length > 0) {
        this.createList(data, false);
       }
    } else {
      data.childGroups.forEach(i => {
        this.list.push({'name': i.name, 'class': 'child'});
        if (i.childGroups && i.childGroups.length > 0) {
          this.createList(i, false);
        }
      });
    }
    console.log(this.list);
  }
}

Called from the parent component like this:

  <div *ngIf="mappings">
    <list-item [data]="mappings.childGroups"></list-item>
  </div>
2 Answers

You describing a tree. Try using some sort of tree component. For example prime ng has a tree component.

I ended up scrapping the dummy component, building the lists directly in my component before showing it - so it will be easier to display changes immediately.

  createSummary() {
    this.listFinished = false;
    this.mappings.childGroups.forEach(item => {
      this.list = [];
      this.createList(item, true);

      this.list.forEach(i => {
        if (i.active) {
          this.list[0].active = true;
        }
      });
      this.lists.push(this.list);
    });
    this.listFinished = true;
  }

  createList(data, parent?) {
    if (parent) {
       this.list.push({'name': data.name, 'class': 'parent', 'active': false});
       if (data.childGroups && data.childGroups.length > 0) {
        this.createList(data, false);
       }
    } else {
      data.childGroups.forEach(i => {
        this.list.push({'name': i.name, 'class': 'child', 'active': i.active});
        if (i.childGroups && i.childGroups.length > 0) {
          this.createList(i, false);
        }
      });
    }
}

Then I show it like this:

  <div *ngIf="listFinished">
    <ul *ngFor="let list of lists">
      <div *ngFor="let item of list">
        <li [class]="item.class" *ngIf="item.active">
          {{item.name}}
        </li>
      </div>
    </ul>
  </div>
Related