How do I use Angular to dynamically change icons individually using *ngFor?

Good day! Currently, I am developing a webpage using Angular and aiming to incorporate a feature that enables users to post comments and mark them with a star if desired. Below is the snippet of the HTML section:

<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>

Additionally, here's the behind-the-scenes logic:

 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));
  }

Please note: The dialogServis component used in this code can be found at Nebular

The current implementation works well, but upon testing with multiple comments, clicking on a star activates both simultaneously. How can I modify the code to allow separate star activation for each individual comment?

Answer №1

It seems like the issue lies in having only one isFavorite switch for all comments at the same level. This means that if it is true, all comments will be marked as favorites logically.

A quick fix would be to store the position of the favorite comment instead of just a boolean flag.

Then, in the template, you can 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 keep track of 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 works well for testing purposes, but for a real app, there are some improvements to consider:

  • Each comment should have an ID along with its content. This way, you can use the ID instead of the index for better management, especially when dealing with actions like deleting comments.
  • Consider splitting your components into a CommentList and Comment. The Comment component can then receive a boolean flag "isFavorite" from the CommentList component, which handles the logic for determining the favorite comment.

Answer №2

@code-gorrila is correct, the approach of having one favorite comment per item makes sense. However, if every comment can be a favorite, then it would be necessary to store isFavorite as a property of each 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>

Answer №3

This suggestion follows the principles of angular best practices: smart and dummy components

It is recommended to create two distinct components:

  • comment-box-component: containing input and comment-component.
  • comment-component: consisting of comment text and star state.

Child Component

comment-component has both input (comment text) and output (emitting changes in favorite state to parent)

In 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 serves as a container that listens for comment events (favorite)

In HTML file.

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

In typescript file.

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

 handleFavoriteState(favorite: boolean, commentId: string) {
  // This method does not focus on performance optimization, it's illustrative
  this.comment = this.comments.map(comment => {
   if (comment.id === commentId) {
     comment.favorite = favorite;
   }
   return comment;
  });
 }

I recommend diving deeper into understanding smart and dummy components as they align with angular best practices.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using local fonts with Styled Components and React TypeScript: A beginner's guide

Currently, I'm in the process of building a component library utilizing Reactjs, TypeScript, and Styled-components. I've come across the suggestion to use createGlobalStyle as mentioned in the documentation. However, since I am only working on a ...

Customizing the entire application's style based on conditions in Angular 4

Looking to implement a dark mode button for the app. Create a toggle switch for "dark mode". This switch will change a boolean value, "dark-ui", between true and false. When the app component detects dark-ui as true, add the class "dark" to a parent-leve ...

Implement a default dropdown menu that displays the current month using Angular and TypeScript

I am looking to implement a dropdown that initially displays the current month. Here is the code snippet I have used: <p-dropdown [options]="months" [filter]="false" filterBy="nombre" [showClear] ...

Seamlessly incorporating Storybook with Tailwind CSS, Create Next App, and TypeScript

*This is a new question regarding my struggles with integrating Storybook and Tailwind CSS. Despite following the recommended steps, I am unable to make Tailwind work within Storybook. Here's what I've done: I started a TypeScript project from s ...

Unpacking nested JSON objects with key-value pairs at various depths using TypeScript within a Node environment

Currently, I am faced with the task of parsing data at various levels in order to retrieve it while maintaining a consistent format. My project involves using TypeScript within the Node environment (specifically Google Cloud), and although I am relatively ...

When hovering over one div, both it and another div should be displayed simultaneously. The second div should continue to be displayed even when hovered over

I am looking to keep another div displayed when hovering over it - in this example, it says "hello." The div I want to remain visible is not a child element of the main div where I initiate the hover event. document.write("<base href=\"" + docum ...

Minimize the count of switch cases that are not empty

How can I optimize the number of cases in my switch statement to align with SonarQube recommendations? Currently, I have 37 cases in a switch statement, but SonarQube recommends only 30. I believe that my code is functioning correctly, and the issue lies ...

Issue: The creation of ComponentClass is restricted due to its absence from the testing module import

The test base for my Angular Cli 6.0.1 app is set up as follows. I am using Jasmine V2.8.0 and Karma V2.0.0 Encountering an error on line 13 that says: Error: Cannot create the component AddressLookUpDirective as it was not imported into the testing mod ...

Facing issue with Angular 17 where pipe is displaying empty data

I am currently utilizing Angular 17 with the code provided below: database.component.html @for(user of (users | userPipe:filters); track user.id) { <tr id="{{ user.id }}"> <td>{{ user.name }}</td> <td> ...

I am interested in forming an object using an array and then looping through the keys of that object

I have a custom object with multiple attributes stored in an array. class Test { a1; a2; a3; a4; a5; } In my project, I have an array that always follows the same order as the attributes of the Test object. arrayWithValues = [a1,a2,a3,a4,a5]; To s ...

Surprising outcome caused by introducing a service dependency within another service in Angular 6

In my current project, I am facing an issue with ngrx-store and Angular 6. Unfortunately, I cannot replicate the problem on the stackblitz, so I will explain it here. I have a Service1 being used in the component, as well as a Service2 that is used within ...

Access to private members is restricted when redefining a class method

After defining a private member with #, attempting to redefine a member that utilizes this private member will result in the inability to access it: class Foo { #secret = "Keyboard Cat"; method() { console.log(this.#secret); } } ...

Issue "Module not found" arises while trying to import an external JSON file in TypeScript

Working with local JSON files is not an issue for me. I've successfully implemented the following code: import data from "./example.json"; However, I encounter an error when attempting to access remote files like the one below, resulting in a "Canno ...

Properly excluding an abstract typescript class from an external library

I have an external library stored in our GitLab with the following structure: export default abstract class Client{ protected someProperty: string | undefined; protected static get baseUrl(): string { return 'www.test.de'; ...

Leverage a personalized column within a for loop in an Angular template

I have created the code below: table.component.html <div class="mat-elevation-z8"> <table mat-table [dataSource]="tableDataSrc" matSort class="mat-elevation-z8"> <ng-container *ngFor="let col of tableCols"> <ng-container ...

Interface-derived properties

One of the challenges I'm facing is dealing with a time interval encapsulation interface in TypeScript: export interface TimeBased { start_time: Date; end_time: Date; duration_in_hours: number; } To implement this interface, I've created ...

Improving efficiency in processing multiple datasets through optimized code

Hey there, I've been given a task to optimize a program that is running too slow. The goal is to make it run faster. interface Payroll { empNo: string; vacationDays: number; } interface AddressBook { empNo: string; email: string; } interfac ...

Sustaining connection between Angular 4 user interface and JAVA backend

I'm currently working on developing a card holder application that utilizes Angular 4 for the frontend and Spring Rest Service for the backend. Within the application, there is a login screen that grants users access to various screens such as transac ...

What are the steps to transform my current node application into TypeScript?

I have a functioning node project that was initially written in JavaScript. The app is quite simple, mostly just hosting a static folder, with some web pages successfully utilizing TypeScript for client-side JavaScript. Now, I am attempting to convert my N ...

Automated Integration with Visual Studio Team Services: Empowering ASP.NET Core and Angular 4 Collaboration

I am currently working on an ASP.NET Core web app using Visual Studio 2017, and I am developing an Angular 4 front-end project in Visual Studio Code. The goal is for the Angular 4 project to be integrated with the Core web app, and I need to set up continu ...