Uğur Dinç
Uğur Dinç

Reputation: 2455

How to preserve class in *ngFor while the array is updating?

I have a simple list that needs to have one of its items selected on click. However, every x seconds the list "refreshes", and the selection seems to get lost.

To add the selection back to the newly created element, I introduced setTimeout, but that seems to have a "flashing" effect.

setInterval( () => {
  this.locations.forEach((o, i, a) => a[i] = a[i] + 's'); // update locations

  setTimeout(() => {
    if (this.selectedLocationID) document.getElementById(this.selectedLocationID).classList.add('selectedLocation');
  }, 0); 

}, 1000);

How to prevent the "flashing" as seen in the plunk?

Upvotes: 1

Views: 601

Answers (2)

Rahul Gupta
Rahul Gupta

Reputation: 40

Here you go buddy

//our root app component
import {Component, NgModule, VERSION} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Component({
selector: 'my-app',
template: `
<h3> Select an item </h3>
<div *ngFor="let location of locations; let i=index;">
<div [ngClass]="{'selectedLocation':selectedLocation==i}">
  <li (click)="selectLocation(i)">Hello {{location}}</li>
</div>
</div>
`
})
export class App {
selectedLocation;
locations: Array = ["World", "Mars", "Saturn", "Pluto"];

constructor() {
// datasource updates every sec
setInterval( () => {
  this.locations.forEach((o, i, a) => a[i] = a[i] + 's'); // update locations

  // if (this.selectedLocationID) document.getElementById(this.selectedLocationID).classList.add('selectedLocation');

  setTimeout(() => {
    //if (this.selectedLocationID) document.getElementById(this.selectedLocationID).classList.add('selectedLocation');
  }, 0); 

}, 1000);
}

selectLocation(i) {
this.selectedLocation = i;
}
}

@NgModule({
 imports: [ BrowserModule ],
 declarations: [ App ],
 bootstrap: [ App ]
})
export class AppModule {}

Upvotes: 1

jmorganmartin
jmorganmartin

Reputation: 343

Every time this runs:

this.locations.forEach((o, i, a) => a[i] = a[i] + 's'); // update locations

the *ngFor completely re-draws the HTML from scratch, removing the class you added on the click:

<div *ngFor="let location of locations; let i = index;">
  <li id="location-{{i}}" (click)="selectLocation($event.target.id)">Hello {{location}}</li>
</div>

The trick is to ensure this class, if set, remains on the re-draw. Something like this (full solution here: https://plnkr.co/edit/6PTWU6dpQLSa5c3tVDbg):

<div *ngFor="let location of locations; let i = index;">
  <li id="location-{{i}}" (click)="selectLocation($event.target.id, i)" [ngClass]="{selectedLocation: selectedLocationIndex == i}">Hello {{location}}</li>
</div>

Rather than tracking the HTML element ID, my solution just tracks the index of the *ngFor loop, which we were already tracking with let i = index (passing $event.target.id is no longer required, but won't hurt anything).

We then use [ngClass] to set/remove the 'selectedLocation' class if the index of our loop, i, matches the index of the selected item that we are now tracking with selectedLocationIndex.

Upvotes: 1

Related Questions