Angular Component Flickering on Observable Data Update

48 Views Asked by At

I am working on an Angular component (GestionAchatTpvGenerationProgressBarComponent) designed to display a progress bar based on data from an Observable. This component is standalone and uses the OnPush change detection strategy. The progress bar is intended to reflect the advancement of certain purchases processed through TPV (Point of Sale Terminal), updating through a data stream (achatTitreViaTpv).

However, each time the achatTitreViaTpv data updates, the entire component appears to "flicker" or refresh visibly, leading to a degraded user experience. I would like to understand why this behavior occurs and how I can avoid or minimize it.

Here is the simplified code of the component:

@Component({
  selector: 'app-gestion-achat-tpv-generation-progress-bar',
  templateUrl: './gestion-achat-tpv-generation-progress-bar.component.html',
  styleUrls: ['./gestion-achat-tpv-generation-progress-bar.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    TranslateModule,
    NgIf,
    AsyncPipe,
    DecimalPipe
  ],
  standalone: true
})
export class GestionAchatTpvGenerationProgressBarComponent {
  @Select(GestionAchatTpvDetailState.recupereAchat) achatTitreViaTpv!: Observable<AchatTitreViaTpv>;
}

And the associated template:

<div class="progress-bar-tpv-generation-container">
    <ng-container *ngIf="achatTitreViaTpv | async as achat">
        <div class="progress-bar-tpv-generation-container__titles-container">
            <!-- Display of progress and estimated remaining time -->
        </div>
        <div class="progress-bar-tpv-generation-container__progress-bar">
            <div class="progress-bar-tpv-generation-container__progress-bar--fill" [style.width]="(condition) + '%'"></div>
        </div>
    </ng-container>
</div>

What I've tried:

  • Ensuring data updates are optimized to avoid unnecessary re-emissions.
  • Using the OnPush change detection strategy to minimize change checks.

Despite these attempts, the flickering persists whenever the data updates. Could someone please point out what might be causing this behavior and how to address it?

2

There are 2 best solutions below

0
ForestG On BEST ANSWER

The issue you describing might be caused by a lot of different things:

  • Are you sure that no other ancestor component re-render in the component's parent tree?
  • How often does the re-render happen? You can subscribe to the stream and see for yourself
export class GestionAchatTpvGenerationProgressBarComponent implements AfterViewInit  {
  @Select(GestionAchatTpvDetailState.recupereAchat) achatTitreViaTpv!: Observable<AchatTitreViaTpv>;

  ngAfterViewInit() {
    this.achatTitreViaTpv.subscribe({next: (val) => console.log("update:" + val); // something like that
  }
}

Having too frequent updates can cause the container to flicker maybe, or if the data has gaps (e.g. the progress jumps from 4% to 5% to null to 6

  • What happens, if you remove the async part from the ngIf and check only for null once? *ngIf="achatTitreViaTpv | async as achat" -> *ngIf="achatTitreViaTpv != null"

  • if the change is happening too many times(multiple times in a second), you might want to introduce some throttle mechanisms. You already use RxJS in Angular, so it might be wise to try https://rxjs.dev/api/operators/throttle

0
dannybee82 On

The problem is that Angular's Change Detection system still is triggered many times. A solution is to use Angular Signals.

The code of the changed component:

import { OnInit, WritableSignal, signal } from '@angular/core';

//(...)

export class GestionAchatTpvGenerationProgressBarComponent implements OnInit {
  
    achatTitreViaTpv?: Observable<AchatTitreViaTpv>;
  
    achatTitreViaTpvSig: WritableSignal<AchatTitreViaTpv | undefined> = signal(undefined);
  
    ngOnInit(): void {
      if(this.achatTitreViaTpv) {
        this.achatTitreViaTpv.subscribe((data: AchatTitreViaTpv) => {
          //TODO: Check for changes in the line below:
          if(data.id != this.achatTitreViaTpvSig()?.id ?? 0) {
            this.achatTitreViaTpvSig.set(data);
          }
        });
    }
  } 
}

Changed line of the ng-container:

<ng-container *ngIf="this.achatTitreViaTpvSig()">

I hope this helps.

Edit: When it's not working, can you show more code, please?