Machtyn
Machtyn

Reputation: 3272

How to show first row of an array and then the subsequent rows in angular2

Is it possible to show just the first row of an array on a particular line, then show the rest of the array in a separate location (such as a new table or card).

For example, I have the following data:

public records = [{
   firstName: "Joe",
   lastName: "Smith",
   favBooks: [ "1984", "Animal Farm", "Lord of the Rings", "Where the Sidewalk Ends"]
},
{
   firstName: "Jane",
   lastName: "Jameson",
   favBooks: [ "Harry Potter", "Ender's Game", "Chronicles of Narnia"
},
{
   firstName: "Sam",
   lastName: "Baker",
   favBooks: "The Pelican Brief"
}]

And I want the output to appear as follows :

tr.even {
  background-color: #C9D4D9;
}
th#accordian-icon 
td[name="accordian-icon"]
{
  width: 15px;
}
th#person-name 
td[name="person-name"]
{
  width: 250px;
}
th#fav-book 
td[name="fav-book"]
{
  width: 250px;
}
<table>
  <thead>
  <tr>
    <th id="accordion-icon"></th>
    <th id="person-name">Name</th>
    <th id="fav-book">Favorite Book(s)</th>
  </tr>
  </thead>
  <tbody>
  <tr>
    <td name="accordion-icon">+</td>
    <td name="person-name">Joe Smith</td>
    <td name="fav-book">1984</td>
  </tr>
  <tr class="even">
    <td name="accordian-icon">-</td>
    <td name="person-name">Jane Jameson</td>
    <td name="fav-book">Harry Potter</td>
  </tr>
  <tr class="even">
    <td name="accordian-icon"></td>
    <td name="person-name"></td>
    <td name="fav-book">Ender's Game</td>
  </tr>
  <tr class="even">
    <td name="accordian-icon"></td>
    <td name="person-name"></td>
    <td name="fav-book">Chronicles of Narnia</td>
  </tr>
  <tr class="">
    <td name="accordian-icon"></td>
    <td name="person-name">Sam Baker</td>
    <td name="fav-book">The Pelican Brief</td>
  </tr>
  </tbody>
</table>

Here is what I have so far (where accordionToggle is an icon name):

<table>
  <thead>
  <tr>
    <th id="accordion-icon"></th>
    <th id="person-name">Name</th>
    <th id="fav-book">Favorite Book(s)</th>
  </tr>
  </thead>
  <tbody *ngFor="let record of records; let even = even">
  <tr [ngClass]="{even: even}"
      (click)="onTabClick($event)">
    <td name="accordion-icon">
      <i class="material-icons">{{accordionToggle}}</i>
    </td>
    <td name="person-name">{{record.firstName}} {{record.lastName}}</td>
    <td name="fav-book">{{record.favBooks[0]}}</td>
  </tr>
  <template [ngIf]="showBooks">
  <tr *ngFor="let book of favBooks"
      [ngClass]="{even:even}">
    <td name="accordian-icon"></td>
    <td name="person-name"></td>
    <td name="fav-book">{{book}}</td>
  </tr>
  </template>
  </tbody>
</table>

The problems I'm having are:

  1. Obviously, in the second set of rows, I have two bindings and that won't work. How do I get the ngIf and the ngFor to work together? (If I put the ngIf in a separate div that wraps the tr, the HTML treats the new set of rows as a different table in the first cell of the big table.) a. I fixed this issue by using the template element.
  2. The first book is repeated on the second row.
  3. When I click on any of the main rows (the ones with the name on it), all rows open up. I only want the row I clicked on to open. I'm sure with a bit more searching, I'll figure out how to pass the actual row being clicked on, but if there's an easy answer, I wouldn't mind it.
  4. The last row only has 1 book, and does not need to be expanded. I want to make it unclickable, but I'm not sure how to do that.

Upvotes: 0

Views: 3086

Answers (1)

yurzui
yurzui

Reputation: 214077

  1. That's right. You can also wrap it in <ng-container *ngIf> instead of <template [ngIf]
  2. Use SlicePipe like record.favBooks | slice: 1
  3. You can use some property like record.opened and switch it by clicking
  4. Make sure that your favBooks property is always an array (see ngOnInit below or use ArrayifyPipe pipe as described here Does NgFor support Arrays containing one Object). Then you can just make condition like record.favBooks.length > 1 to do desired logic on your view

Plunker Example

Here is the code:

view

<table>
  <thead>
  <tr>
    <th id="accordion-icon"></th>
    <th id="person-name">Name</th>
    <th id="fav-book">Favorite Book(s)</th>
  </tr>
  </thead>
  <tbody *ngFor="let record of records; let even = even">
    <tr [ngClass]="{even: even, clickable: record.favBooks.length > 1}" (click)="record.favBooks.length > 1 ? record.opened = !record.opened : false">
      <td name="accordion-icon">
        <i [ngClass]="{'material-icons': record.favBooks.length > 1, opened: record.opened}"></i>
      </td>
      <td name="person-name">{{record.firstName}} {{record.lastName}}</td>
      <td name="fav-book">{{record.favBooks[0]}}</td>
    </tr>
    <template [ngIf]="record.opened && record.favBooks.length > 1">
      <tr *ngFor="let book of record.favBooks | slice: 1" [ngClass]="{even:even}">
        <td name="accordian-icon"></td>
        <td name="person-name"></td>
        <td name="fav-book">{{book}}</td>
      </tr>
    </template>
  </tbody>
</table>

component:

export class AppComponent {
  records = [{
    firstName: "Joe",
    lastName: "Smith",
    favBooks: ["1984", "Animal Farm", "Lord of the Rings", "Where the Sidewalk Ends"]
  },
  {
    firstName: "Jane",
    lastName: "Jameson",
    favBooks: ["Harry Potter", "Ender's Game", "Chronicles of Narnia"]
  },
  {
    firstName: "Sam",
    lastName: "Baker",
    favBooks: "The Pelican Brief"
  }]

  ngOnInit() {
    this.records.forEach((x: any) => {
      x.favBooks = Array.isArray(x.favBooks) ? x.favBooks : [x.favBooks];
    })
  }
};

Upvotes: 2

Related Questions