angularpocketbase

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


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>

Solution

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