Angular / double nested ngFor only in a specific case

959 views Asked by At

Hello a service is returning to me the available cards of a client like this:

{
"cards": [
    {
        "id": "545451",
        "name": "Visa",
        "specialConditions": [
            {
                "type": "cardFees",
                "contribution": [
                    {
                        "conditionCode": "freeFees",
                        "validFrom": "2019-12-31",
                        "validTo": "4712-12-31"
                    }
                ]
            }
        ]
    },
    {
        "id": "434342",
        "name": "Mastercard"
    }
]
}

as you can see the cards are an Array the SpecialConditions of it also an Array and the contribution is also an Array.

What i want is to create a data table that displays in 1 line the card.name + the card.specialCondition.type + the card.specialCondition.contribution.conditionCode but i also want to display this line ONLY when a card has a contribution. That means that the mastercard card in the payload i posted would never show in this table

How i came to the result i approach right now is the following code

<div *ngIf="products.cards.length > 0">
<table class="table table-bordered">
<thead>
    <tr>
        <th scope="col">Name</th>
        <th scope="col">Type</th>
        <th scope="col">Condition Type</th>
        <th scope="col">From</th>
        <th scope="col">To</th>
    </tr>
</thead>
<tbody>
    <tr *ngFor="let card of products.cards; let i = index">
        <td *ngIf="card.specialConditions">{{card.name}}</td>
        <td *ngFor="let specialCondition of card.specialConditions; let j = index">{{specialCondition.type?specialCondition.type: 'x'}}</td>
        <td *ngFor="let specialCondition of card.specialConditions; let j = index">{{specialCondition.contribution[0].conditionCode?specialCondition.contribution[0].conditionCode: 'x'}}</td>
        <td *ngFor="let specialCondition of card.specialConditions; let j = index">{{specialCondition.contribution[0].validFrom?(specialCondition.contribution[0].validFrom | date:'dd.MM.yyyy'): 'x'}}</td>
        <td *ngFor="let specialCondition of card.specialConditions; let j = index">{{specialCondition.contribution[0].validTo? (specialCondition.contribution[0].validTo | date:'dd.MM.yyyy'): 'x'}}</td>
    </tr>
</tbody>
</table>
</div>

But i am quite sure that there is a much better approach as that. But i couldnt find out. When i try to wrap the specialConditions ngFor in a span or div or a new html element the table was showing very weird thats why i decided to create for each line a new ngFor

1

There are 1 answers

0
Eliseo On

The only you need is ng-container

As you has an array inside an array inside an array looks like more complex than really is but you can do some like

<tbody>
    <ng-container *ngFor="let card of products.cards; let i = index">
      <ng-container *ngIf="card?.specialConditions as conditions">
        <ng-container
          *ngFor="let conditions of card.specialConditions; let firstCondition = first"
        >
        <tr *ngFor="let contibution of conditions.contribution;let firstContribution=first">
          <td>{{ firstCondition?card.name:'' }}</td>
          <td>{{ firstContribution?conditions.type:'' }}</td>
          <td>{{ contibution.conditionCode || 'x' }}</td>
          <td>{{ contibution.validFrom }}</td>
          <td>{{ contibution.validTo }}</td>
        </tr>
        </ng-container>
      </ng-container>
      <ng-container *ngIf="!card?.specialConditions">
        <tr>
          <td>{{ card.name }}</td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
      </ng-container>
    </ng-container>
  </tbody>

NOTE: You need convert To object javaScript Date your "dates". You need a function like

  transform(cards)
  {
    cards = cards.map((card:any) => {
      if (card.specialConditions)
      {
        card.specialConditions.forEach((condition:any)=>{
          console.log(condition)
          condition.contribution.forEach(x=>{
            console.log(x)
            x.validFrom=new Date(x.validFrom)
            x.validTo=new Date(x.validTo)
          })
        })
      }
      return card
    });
  }

And you pass as

this.transform(this.products.cards)

In the little stackblitz I use in ngOnInit, but really I like more this transform function belong to the "service" (I imagine your data comes from a service).