user14239866
user14239866

Reputation:

Angular how can I make a sepperated Icon Change with *ngFor?

Hi I'm working on a webpage in Angular and want to add a feature that can allow the User to write Comments and mark them with a Star if they want. Here is the HTML part:

<ul>
  <li class="realitive" *ngFor="let comment of comments">
    {{ comment }} 
    <nb-icon pack="font-awesome" class="fa-star absulute"  [ngClass]="{ 'fas': isFavorite,' far': !isFavorite}" (click)="onClick()"></nb-icon>
  </li>
 </ul>

and here the Logic behind this:

 comments: string[] = [];

 @Input("isFavorite") isFavorite: boolean;
 @Output("change") change= new EventEmitter();

 onClick() { 
    this.isFavorite = !this.isFavorite;
    this.change.emit({ newValue: this.isFavorite });
  }

  addComment() {
    this.dialogService.open(CommentComponent)
      .onClose.subscribe(comment => comment && this.comments.push(comment));
  }

NOTE: the dialogServis is from Nebular

this is working fine but if I add more than One Comment for testing and click on the star Both are getting active how can I tweak the code so they get change Separately

Upvotes: 0

Views: 376

Answers (3)

Ruben Helsloot
Ruben Helsloot

Reputation: 13129

@code-gorrila is right, if you can have one favorite comment per item. If every comment can be a favorite, though, you need to store isFavorite as a property of comment:

interface IComment {
  comment: string;
  isFavorite: boolean;
}
 comments: IComment[] = [];

 @Output("change") comments = new EventEmitter();

 onClick(comment) { 
    comment.isFavorite = !comment.isFavorite;
    this.change.emit(this.comments);
  }

  addComment() {
    this.dialogService.open(CommentComponent)
      .onClose.subscribe(comment => comment && this.comments.push({ comment, isFavorite: false }));
  }
<ul>
  <li class="realitive" *ngFor="let comment of comments">
    {{ comment.comment }} 
    <nb-icon pack="font-awesome" class="fa-star absulute"  [ngClass]="{ 'fas': comment.isFavorite,' far': !comment.isFavorite}" (click)="onClick(comment)"></nb-icon>
  </li>
</ul>

Upvotes: 0

Luis Reinoso
Luis Reinoso

Reputation: 758

This response is based on angular best practices: smart and dummy components

You could create two components

  • comment-box-component: it contain the input and comment-component.
  • comment-component: it contain only comment text and star state.

Child component

comment-component It has the input (commment text) and output (emit to parent favorite state has change)

On typescript file.

 @Input() isFavorite: boolean;
 @Output() favorite: EventEmitter<boolean> = new EventEmitter();

 selectAsFavorite() {
   this.favorite.emit(true);
 }

 unselectAsFavorite() {
   this.favorite.emit(false);
 }

Parent component

comment-box-component Its a container that listen for comment events (favorite)

On HTML file.

 <input type="text"/> 
 <ng-container *ngFor="let comment of comments">
   <comment-component [comment]="comment" (favorite)="handleFavoriteState($event, comment.id)">
 </ng-container>

On typescript file.

 comments = [{id: 1, text: 'hi', favorite: false}, {id: 2, text: 'bye', favorite: false}] // They are just examples

 handleFavoriteState(favorite: boolean, commentId: string) {
  // this isn't the best performance approach is just examplenary
  this.comment = this.comments.map(comment => {
   if (comment.id === commentId) {
     comment.favorite = favorite;
   }
   return comment;
  });
 }

I suggest to review about smart and dummy components. It's an angular best practice.

Upvotes: 0

code-gorilla
code-gorilla

Reputation: 2418

The problem is that you have only 1 isFavorite switch on the same level of your comments variable. So if it is true, all your comments will be favorites logic-wise.

The simplest solution would be store the position of the favorite comment instead of just a boolean flag.

So in the template you would do:

  <li class="realitive" *ngFor="let comment of comments; index as i">
    <nb-icon pack="font-awesome" class="fa-star absulute"  [ngClass]="{ 'fas': favoriteComment === i,' far': favoriteComment !== i}" (click)="onClick(i)"></nb-icon>
  </li

In your component you would then store the index of the favorite comment:

 @Input("favoriteComment") favoriteComment: number;
 @Output("change") change= new EventEmitter();

 onClick(index: number) { 
    this.isFavorite = index;
    this.change.emit({ newValue: this.favoriteComment });
  }

This solution is fine for playing around with it, but for a production app you might want to handle it differently:

  • Each comment should not only be a string, but also have an id (e.g. from a database). This id could then be used instead of the index in the list. E.g. think about the case that a user deletes a comment.
  • You should split up your components into a CommentList and a Comment. Then the Comment component would just receive the boolean flag "isFavorite" as the CommentList component can to the evaluation of the favorite comment.

Upvotes: 1

Related Questions