Angular: slow template refresh with async pipe when tab is in background

119 views Asked by At

In my Angular project, I have a component where I use ngOnInit to subscribe to a WebSocket. This subscription listens for any changes in a specific database table. When a change occurs, I fetch the updated data from the database and display it in my template. I'm using PocketBase to manage both the remote data and the WebSocket connection.

The problem is when the web application is left idle or is moved to a background tab. In such scenarios, even though I can see immediate network requests and responses for getWaitingQueueEntries and getServingQueueEntries (as verified through the browser's network development tool), the actual template takes significantly longer to update with the new data.

Interestingly, if I refresh the web page, the data updates almost instantly, but the problem recurs if I again put the page or tab in the background.

Here is the code:

waitingEntries$: Observable<QueueResponse[] | undefined> | undefined;
  servingEntries$: Observable<QueueResponse[] | undefined> | undefined;

  ngOnInit() {
    // refresh the observables
    this.waitingEntries$ = this.backend.getWaitingQueueEntries(this.group!.id);
    this.servingEntries$ = this.backend.getServingQueueEntries(this.group!.id);
    // websocket subscription to any queue table changes
    this.backend
      .getPocketBaseInstance()!
      .collection(Collections.Queue)
      .subscribe('*', (update: RecordSubscription<QueueResponse>) => {
        if (update.record.group != this.group!.id) return;
        // refresh the observables
        this.waitingEntries$ = this.backend.getWaitingQueueEntries(this.group!.id);
        this.servingEntries$ = this.backend.getServingQueueEntries(this.group!.id);
        console.log('UPDATING');
      });
  }

Service:

  getServingQueueEntries(groupId: string): Observable<QueueResponse[] | undefined> {
    return this.getQueueEntries(groupId, 'serving');
  }

  getWaitingQueueEntries(groupId: string): Observable<QueueResponse[] | undefined> {
    return this.getQueueEntries(groupId, 'waiting');
  }

  getQueueEntries(groupId: string, status: string): Observable<QueueResponse[] | undefined> {
    return from(
      this.pocketBaseInstance
        ?.collection(Collections.Queue)
        .getFullList<QueueResponse>({
          filter: `group="${groupId}"&&status="${status}"`,
          sort: '-updated'
        })
    );
  }

Template:

  <div class="col-6 fw-bold text-secondary ps-4 bottom-dashes" *ngFor="let entry of waitingEntries$ | async">
    <div style="font-size: 5.5vw;">
      {{ formatNumber(entry.number) }}
    </div>

  </div>

  <div class="col-12 fw-bold text-success ps-4 bottom-dashes" *ngFor="let entry of servingEntries$ | async">
    <div style="font-size: calc(1.525rem + 8vw); margin-top: -1.5rem;">
      {{ formatNumber(entry.number) }}
    </div>
  </div>
2

There are 2 answers

0
NiceToMytyuk On BEST ANSWER

The issue seems to be linked to ngZone, it appears that the promises returned by PocketBase's functions are not registered within Angular's ngZone.

I've been able to resolve the issue by wrapping all the functions that return a Promise from the PocketBase with ngZone.run.

Like this in component:

  ngOnInit() {
    // refresh the observables
    this.getData();

    this.backend
      .getPocketBaseInstance()!
      .collection(Collections.Queue)
      .subscribe('*', (update: RecordSubscription<QueueResponse>) => {
        if (update.record.group != this.group!.id) return;
        // refresh the observables
        this.ngZone.run(() => {
          this.getData();
        });
      });
  }

  getData() {
    this.backend.getWaitingQueueEntries(this.group!.id).then(data => {
      if (data) this.waitingEntries = data;
    });
    this.backend.getServingQueueEntries(this.group!.id).then(data => {
      if (data) this.servingEntries = data;
    });
  }

And in the service:

  getServingQueueEntries(groupId: string): Promise<QueueResponse[] | undefined> {
    return this.getQueueEntries(groupId, 'serving');
  }

  getWaitingQueueEntries(groupId: string): Promise<QueueResponse[] | undefined> {
    return this.getQueueEntries(groupId, 'waiting');
  }

  getQueueEntries(groupId: string, status: string): Promise<QueueResponse[] | undefined> {
    return this.pb
        ?.collection(Collections.Queue)
        .getFullList<QueueResponse>({
          filter: `group="${groupId}"&&status="${status}"`,
          sort: '-updated'
        });
  }
1
Hezy Ziv On

When an Angular application is in a background tab or the browser is minimized, the change detection mechanism slows down to conserve resources. however you can do it manually

inject ChangeDetectorRef

constructor(private cdr: ChangeDetectorRef, private backend: BackendService) {}

trigger change detection

this.backend
   .getPocketBaseInstance()!
   .collection(Collections.Queue)
   .subscribe('*', (update: RecordSubscription<QueueResponse>) => {
     if (update.record.group != this.group!.id) return;
     // refresh the observables
     this.waitingEntries$ = this.backend.getWaitingQueueEntries(this.group!.id);
     this.servingEntries$ = this.backend.getServingQueueEntries(this.group!.id);
     console.log('UPDATING');

     // Manually trigger change detection
     this.cdr.detectChanges();
   });

if that will not help take a look at NgZone