Angular Change Detection Gives Up After secondary data load on SPA

I have a SPA that ultimately lists out a lot of data, but in batches.

I created a component at the bottom of the list, with a ‘Visibility’ directive so that when it is visible we make a new request to the dataset in a SQL server to get the next batch.

html-tag-for-component

    <app-infinity-scroll 
      [(pageNumber)]="page" 
      [displayPage]="displayPage" 
      [authd]="authd"
      [done]="done" 
      [numResults]="displayPage == 'tiles-hub' ? hubs.length : wallets.length"
      class="{{scrollVisible ? '' : 'hiddenDisplay'}}"
      trackVisibility
    ></app-infinity-scroll>

component-to-trigger-data-call

import { outputAst } from '@angular/compiler';
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { DbSqlService } from 'services/db-sql.service';
import { TokenAuthService } from 'services/token-auth.service';
import { TrackVisibilityDirective } from 'src/app/directives/track-visibility.directive';
import { SortStyle } from 'src/app/interfaces/mvlot';
import { MatProgressBar } from '@angular/material/progress-bar';

@Component({
  selector: 'app-infinity-scroll',
  templateUrl: './infinity-scroll.component.html',
  styleUrls: ['./infinity-scroll.component.scss']
})
export class InfinityScrollComponent implements OnInit {

  @Input() pageNumber: number;
  @Input() displayPage: string;
  @Input() authd: boolean;
  @Input() done: boolean;
  @Input() numResults: number;

  @Output() pageNumberChange = new EventEmitter<number>();

  lastDisplay = '';
  loading: boolean = true;
  
  constructor(
    private visTrack: TrackVisibilityDirective
    , private cdr: ChangeDetectorRef
    , private dbApi: DbSqlService
    , private authService: TokenAuthService
  ) {
  }

  ngOnInit(): void {
    this.authService.UserAuthd.subscribe((res) => {
      // if (res) {
        this.dbApi.initGetWalletsHandler(0, 50, SortStyle.scoreDesc);
        this.pageNumber = 1;
      // }
    })

    this.visTrack.visibile.subscribe((val) => {
      if (!this.done) {
        this.loading = true;
        if (val) {
          if (this.displayPage == 'tiles') {
            this.dbApi.initGetWalletsHandler((this.pageNumber) * 50, 50, SortStyle.default);
            this.pageNumber += 1;
          }
          if (this.displayPage == 'tiles-hub') {
            this.dbApi.initGetHubsHandler((this.pageNumber) * 50, 50);
            this.pageNumber += 1;
          }
        }
      }
    })
  }
}

Some functions run, call out to a back-end, respond with data, where a listener is waiting.

    this.dbApi.resultObs.subscribe(val => {
      if (val.append != true) {
        this.results = [];
      } 

      if (val.reset) {
        this.page = 1;
      }

      val.data.data.forEach((b: any) => {
        var result: Collection;
        var existingResults = this.results.filter(w => w.ownerId == b.ownerId);
        
        if (existingResults.length == 0) {
          result = {
            ownerId: b.ownerId
            , totalScore: b.modifiedLandScore
            , filteredCount: b.filteredCount
            , totalLots: b.totalLots
            , totalPrice: b.totalPrice
            , name: ''
            , lands: []
            , type: CollectionType.b
          }

          

          result.bs.push(b);
          this.results.push(result);
        } else {
          result = existingResults[0];
          result.bs.push(b);
        }
      });

      this.resultDataSource = new MatTableDataSource(this.results);

      this.collectionType = CollectionType.b;
      this.uiService.loadingBar(false);
      this.done = val.data.data.length == 0;

      this.cdr.detectChanges();
    })

And, finally this is laid out for the user:

          <tr *ngFor="let result of results">
            <td>
              <display-block 
                [collection]="b" 
                [displayVertical]="displayVertical" 
                [displayCaseCount]="displayCaseCount" 
                [gridClassName]="gridClassName" 
                [authd]="authd" 
                [type]="result.type"
                [expanded]="results.length == 1"
                [isPhonePortrait]="isPhonePortrait"
                ></display-block>
            </td>
          </tr>

Everything works fine on the first grab of data.
And everything appears to work fine on the second pull, but for any of the items appended to the view with the second pull, ChangeDetector just seems to give up. I’ll trigger an action, that should modify the view, but nothing happens, unless I manully put in cdr, or I flip to a new window, or something, then they respond.

I’m going to continue trying to find a root cause, but at the moment, I’m out of ideas. There’s no prominent error message that would imply something broke. The items fromt the first batch still work. But the ones from the second will appear to lock up. until CDR is forced by an outside event.

I wanted to check here to see if anyone had any ideas on what may be causing this.
Also, here’s the declaration code for ‘trackVisibility’

import {
  Directive,
  ElementRef,
  EventEmitter,
  NgZone,
  OnDestroy,
  OnInit,
  Output,
} from '@angular/core';

@Directive({
  selector: '[trackVisibility]',
})
export class TrackVisibilityDirective implements OnInit, OnDestroy {
  observer!: IntersectionObserver;

  @Output()
  visibile = new EventEmitter<boolean>();

  constructor(private el: ElementRef<HTMLElement>, private ngZone: NgZone) {}

  ngOnInit(): void {
    this.ngZone.runOutsideAngular(() => {
      this.observer = new IntersectionObserver((entries) => {
        entries.forEach((e) => {
           this.visibile.emit(e.isIntersecting);
        });
      });
      this.observer.observe(this.el.nativeElement);
    });
  }

  ngOnDestroy(): void {
    this.observer.disconnect();
  }
}