Liav Orenstein
Liav Orenstein

Reputation: 1

How to change a variable's value inside a specific child component rendered with ngFor?

I'm sorry if this was already answered but I couldn't find a sufficient solution anywhere. I have a parent component called "users-list" which renders a list of child components called "user" with *ngFor. The class of every component is dynamic and is dependent on several variables inside each component. When the user clicks on a component (and by that, "selects" it, its class changes. For now, the user can select multiple components and I need to make sure only one is selected at any given time. How can I achieve this?

Here's my code:

Parent Component:

import { Component, Input, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { User } from '../user';
import { UtilsService } from '../utils.service';

@Component({
  selector: 'app-users-list',
  templateUrl: './users-list.component.html',
  styleUrls: ['./users-list.component.css']
})
export class UsersListComponent implements OnInit {

  usersList : User[] = []
  filteredUsersList : User[] = []
  usersSub : Subscription = new Subscription

  selectedClassNmae : String = "card-selected"

  searchValue = ""
  search(searchValue : string)
  {
    this.filteredUsersList = this.usersList.filter(x => x.name.includes(searchValue) || x.email.includes(searchValue))
  }

  constructor(private utils : UtilsService) { }

  ngOnInit(): void {
  
    this.usersSub = this.utils.getUsers().subscribe((data : any) =>
    {
      this.usersList = data
      this.filteredUsersList = data
    })

  }

  ngOnDestroy()
  {
    this.usersSub.unsubscribe();
  }

}
<h1>Users List</h1>
<div>
  <div class="searchBox">
    <mat-form-field class="example-form-field" appearance="fill">
      <mat-label>Search By Name</mat-label>
      <input matInput type="text" [(ngModel)]="searchValue" (keyup)="search(searchValue)">
    </mat-form-field>
  </div>
  <div class="addUserButton">
    <button mat-button>Add User</button>
  </div>
</div>
<app-user *ngFor="let user of filteredUsersList" [user]="user"></app-user>

Child Component:

import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core';
import { Subscription } from 'rxjs';
import { User } from '../user';
import { UtilsService } from '../utils.service';

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {

  @Input()
  user : User | undefined

  otherData : boolean = false
  editData : boolean = false

  constructor(private utils : UtilsService) { }

  deleteSub : Subscription = new Subscription
  deleteUser()
  {
    if(confirm("Are you sure you want to delete the user " + this.user!.name) == true)
    {
      this.deleteSub = this.utils.deleteUser(this.user!._id).subscribe()
    }
  }

  editSub : Subscription = new Subscription
  editUser()
  {
    this.editSub = this.utils.editUser(this.user!._id , this.user!).subscribe()
  }

  userSelected : Boolean = false
  selectedUserID : String = ""
  selectUser()
  {
    this.selectedUserID = this.user!._id
    if(this.userSelected == false)
    {
      this.cardClassName = "card-selected"
    }
    else
    {
      this.cardClassName = this.originalClassName
    }
    this.userSelected = !this.userSelected
  }

  cardClassName : String = "card-completed"
  originalClassName : String = "card-completed"

  ngOnInit(): void {

    this.user?.tasks.forEach(task => {
      if(task.completed == false)
      {
        this.cardClassName = "card-uncompleted"
        this.originalClassName = this.cardClassName
      }
    })

  }

  ngOnDestroy()
  {
    this.deleteSub.unsubscribe()
    this.editSub.unsubscribe()
  }

}
.card-completed {
    max-width: 400px;
    background-color: rgb(239, 248, 239);
    border-style: solid;
    border-color: #25a18e;
  }

.card-uncompleted {
    max-width: 400px;
    background-color: rgb(248, 239, 239);
    border-style: solid;
    border-color: #bc4749;
  }

.card-selected {
  max-width: 400px;
  background-color: rgb(248, 239, 222);
  border-style: solid;
  border-color: orange;
}
<mat-card class= "{{cardClassName}}" (click)="selectUser()">
<mat-card-content *ngIf="editData==false">   
  <mat-card-title>{{user?.name}}</mat-card-title>
    <mat-card-subtitle>{{user?.email}}</mat-card-subtitle>
    <mat-card-content *ngIf="otherData">
      {{user?.street}} St.<br/>
      {{user?.city}}<br/>
      {{user?.zipcode}}
    </mat-card-content>
    <mat-card-actions>
      <button mat-button (mouseover)="otherData=true" (click)="otherData=false">{{otherData? "Close Other Data" : "Other Data"}}</button>
      <button mat-button (click)="editData=true">Edit</button>
      <button mat-button (click)="deleteUser()">Delete</button>
    </mat-card-actions>
  </mat-card-content>

  <mat-card-content *ngIf="editData==true"> 
    <form #f="ngForm" (ngSubmit)="editUser()">
      Full Name : <input type="text" name="fName" #fName="ngModel" [(ngModel)]="user!.name"> <br/>
      Email : <input type="text" name="email" #email="ngModel" [(ngModel)]="user!.email"> <br/>
      Street : <input type="text" name="street" #street="ngModel" [(ngModel)]="user!.street"> <br/>
      City : <input type="text" name="city" #city="ngModel" [(ngModel)]="user!.city"> <br/>
      Zipcode : <input type="number" name="zipcode" #zipcode="ngModel" [(ngModel)]="user!.zipcode"><br/><br/>
      <input type="submit"> <input type="button" value="Cancel" (click)="editData=false">
    </form>
  </mat-card-content>  
  

  </mat-card>

  <br/>

Upvotes: 0

Views: 567

Answers (1)

Arnaud Denoyelle
Arnaud Denoyelle

Reputation: 31215

I would just use a variable to keep track of the selected element + [ngClass] to conditionally add a CSS class :

Typescript :

export class AppComponent {
  selectedElt: any;

  items = [
    { id: 1, name: 'foo' },
    { id: 2, name: 'bar' },
    { id: 3, name: 'kix' },
  ];
}

HTML :

<ul>
  <li
    *ngFor="let item of items"
    (click)="selectedElt = item"
    [ngClass]="{ selected: selectedElt === item }"
  >
    {{ item.name }}
  </li>
</ul>

CSS :

.selected {
  color: red;
}

And the StackBlitz

Upvotes: 1

Related Questions