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.