Home > Software design >  How to hide/show a button in a tablerow depending on the data in that row in Angular?
How to hide/show a button in a tablerow depending on the data in that row in Angular?

Time:01-25

I am trying to make an user table in which all users from my mongoDb are shown. The user can follow or unfollow other users by clicking on a button inside the row of the table. If the current user already follows one of the users i want to show the unfollow button and vice versa.

Here is an image of the ui to give you a better understanding, "volgen" meaning follow.

Image: table of users

The problem i have is that i use a method named "isFollowing(id: string)" inside an *ngIf expresion which is called infinitely. I am not sure why.

I have read about angular re evaluating ngIf expresions many times and using methods inside ngIf expression isn't best practise. Most suggest declaring a boolean variable instead of using a method. In my case this wont work, since the (un)follow button depends on the data that is provided in the ngFor. Does anyone know how i should approach this problem?

Here is the isFollowing method:

//check if the current user already follows the other user
  isFollowing(otherUser: string | undefined): boolean {
    console.log('isFollowing called from user-list.component.ts');
    if (otherUser === undefined) {
      SweetAlert.showErrorAlert('Er gaat iets mis, probeer het opnieuw..');
      return false;
    }
    if (this.currentUser.followingUsers?.includes(otherUser)) {
      SweetAlert.showSuccessAlert('Je hebt deze gebruiker ontvolgt.');
      return true;
    }
    SweetAlert.showErrorAlert('Er gaat iets mis, probeer het opnieuw..');
    return false;
  }

Here is the html:

          <tbody *ngIf="users.length > 0">
            <tr *ngFor="let user of filteredUsers; let i = index">
              <th scope="row">{{ i   1 }}</th>
              <td>{{ user.firstName | titlecase }}</td>
              <td>{{ user.lastName | titlecase }}</td>
              <td>{{ user.city | titlecase }}</td>
              <td *ngIf="isUser && !isFollowing(user._id)">
                <a
                  (click)="followUser(user._id)"
                  
                >
                  Volgen
                </a>
              </td>
              <td *ngIf="isUser && isFollowing(user._id)">
                <a
                  (click)="unfollowUser(user._id)"
                  
                >
                  Ontvolgen
                </a>
              </td>

              <td *ngIf="isAdmin">
                <a
                  (click)="sweetAlertDeleteConfirmation(user._id)"
                  
                >
                  Verwijderen
                </a>
              </td>
            </tr>
          </tbody>

CodePudding user response:

The right way would be to use a pure pipe (read more on this topic here in the official Angular documentation). This means the value will only be re-evaluated when the array instance changes (so on each change you would need to define a new array for followingUsers property).

In your case you could make an IncludesPipe which could look like this:

@Pipe({
  name: 'includes'
})
export class IncludesPipe<T> implements PipeTransform {
  transform(array: T[], item: T): boolean {
    return array.includes(item);
  }
}

You can then use it like this in your view:

<td *ngIf="user.followingUsers | includes : user._id; then unfollowTemplate else followTemplate"></td>
<ng-template #followTemplate>
  <a (click)="followUser(user._id)" >Volgen</a>
</ng-template>
<ng-template #unfollowTemplate>
  <a (click)="unfollowUser(user._id)" >Ontvolgen</a>
</ng-template>

In this template example I use a shorthand then and else blocks, which I think is more readable and the condition needs to only be evaluated once instead of twice.

If you don't want to change followingUsers to a new array on each change you would need to manually trigger change detection for this example to work properly.

  • Related