'Angular Material table filtering out all results when input is initially blank, will work after typing into input Only

So my table works really well, except that it shows no data until either a row is clicked or the filter is used. I am unsure as to what is going on here, not sure what I am doing wrong.

I just want all data to initially load visibly into the table.

HTML:

<div fxLayoutAlign="center center">
    <button mat-mini-fab color="accent" style="margin-right: 20px">
      <mat-icon class="add-course-btn" (click)="stalkUser()">person_add_alt_1</mat-icon>
    </button>
      <mat-form-field fxFlex="40%">
        <input matInput (keyup)="doFilter($event)" placeholder="Ex. Username" #input>
      </mat-form-field>
    </div>
    
    <ng-container *ngIf="usersUserStalking.length > 0 && isLoading == false">
      <mat-table [dataSource]="dataSource" matSort>
        <ng-container matColumnDef="userName">
            <mat-header-cell *matHeaderCellDef mat-sort-header id="nameField">Name</mat-header-cell>
            <mat-cell *matCellDef="let element"><span style="cursor:pointer" (click)="navigateProfile(element.tag)"><img mat-card-avatar src="{{element.profileImage }}" class="friends-avatar-photo"> {{ element.name}}</span></mat-cell>
          </ng-container>
        
          <ng-container matColumnDef="userTag">
            <mat-header-cell *matHeaderCellDef mat-sort-header fxHide.xs>Tag</mat-header-cell>
            <mat-cell *matCellDef="let element" fxHide.xs>{{element.tag }}</mat-cell>
          </ng-container>
    
          <ng-container matColumnDef="userRank">
            <mat-header-cell *matHeaderCellDef mat-sort-header fxHide.xs>Rank</mat-header-cell>
            <mat-cell *matCellDef="let element" fxHide.xs>{{element.rank }}</mat-cell>
          </ng-container>
        
          <ng-container matColumnDef="factionName" >
            <mat-header-cell *matHeaderCellDef mat-sort-header fxHide.xs>Faction</mat-header-cell>
            <mat-cell *matCellDef="let element" fxHide.xs><span style="cursor:pointer" (click)="navigateProfile(element.tag)">{{element.faction }}</span></mat-cell>
          </ng-container>

          <ng-container matColumnDef="stalking" >
            <mat-header-cell *matHeaderCellDef>Stalking</mat-header-cell>
            <mat-cell *matCellDef="let element"><button mat-stroked-button class="hover-class" color="primary"><span>Stalking</span></button></mat-cell>
          </ng-container>
        
          <!-- Row shown when there is no matching data. -->
          <tr class="mat-row" *matNoDataRow>
            <td class="mat-cell" colspan="4">No data matching the filter "{{input.value}}"</td>
          </tr>
        
          <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
          <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
      </mat-table>
      
      <mat-paginator #paginator 
                    [pageSizeOptions]="[5, 10, 25, 100]">
      </mat-paginator>
    </ng-container>

    <!-- Show if user is stalking no one -->
    <ng-container *ngIf="usersUserStalking.length <= 0 && isLoading == false">
      <div fxLayoutAlign="center center">
        <h1>You are Stalking no one, use the yellow button above to stalk whoever you want!</h1>
      </div>
    </ng-container>
    <ng-container *ngIf="isLoading">
      <div class="spinner-container" fxLayoutAlign="center center">
        <mat-spinner></mat-spinner>
      </div>
    </ng-container>

Typscript:

import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { AngularFirestore } from '@angular/fire/firestore';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Observable } from 'rxjs';
import { User } from 'src/app/auth/user.model';
import { UserService } from 'src/app/auth/user.service';
import { Router } from '@angular/router';
import { finalize } from 'rxjs/operators';

@Component({
  selector: 'app-stalking',
  templateUrl: './stalking.component.html',
  styleUrls: ['./stalking.component.css']
})
export class FollowingComponent implements OnInit {
  @Input() userUID: string;
  userId: string;

  displayedColumns = ["userName", "userTag", "userRank", "factionName", "stalking"];
  dataSource = new MatTableDataSource<User>();
  @ViewChild(MatSort) sort: MatSort;
  @ViewChild(MatPaginator) paginator: MatPaginator;

  //User Array
  userArray$: Observable<unknown[]| null>;
  usersUserStalking: User[] = []
  //loading
  isLoading: boolean = false;

  constructor(private user: UserService,
              private db: AngularFirestore,
              private router: Router) {}

  async ngOnInit() {
   await this.onReadCollection(this.userUID)
  }

  async onReadCollection(userUID: string){
    this.isLoading = true;
    this.db.collection(`users/${userUID}/stalking`).get()
        .pipe(
          finalize(() => {
            this.isLoading = false;
            this.dataSource.sort = this.sort;
            this.dataSource.paginator = this.paginator;
            console.log(this.usersUserStalking)
            this.dataSource.data = this.usersUserStalking; // update dataScource
            console.log(this.dataSource.data)
          })
        )
        .subscribe(snaps => {
         snaps.forEach(snap => {
            this.user.findUserbyUID(snap.id).subscribe(user => {
              this.usersUserStalking.push(<User>user)
            })
         })
     })
    
 }

 

  async getFriendsForProfile(){
    this.userId = this.user.getUserUID();
  }

  ngAfterViewInit() {
    // this.dataSource.sort = this.sort;
    // this.dataSource.paginator = this.paginator;
    // this.dataSource.data = this.usersUserStalking
  }

  doFilter(event: Event | null) {
    const filterValue = (event.target as HTMLInputElement).value;
    this.dataSource.filter = filterValue.trim().toLowerCase();
  }

  navigateProfile(userTag){
    this.router.navigate(['/profile', userTag])    
  }

}

I have my filter set up like this:

html:

      <mat-form-field fxFlex="40%">
        <input matInput (keyup)="doFilter($event)" placeholder="Ex. Username" #input>
      </mat-form-field>

Typescript:

  doFilter(event: Event) {
    const filterValue = (event.target as HTMLInputElement).value;
    this.dataSource.filter = filterValue.trim().toLowerCase();
  }

I feel like I copied the angular material tables exactly like how it is done in the examples shown here: https://material.angular.io/components/table/examples

Edit 1: I updated my code and get on initial user to be visible now, but still generally have the same issue, I only see all users in the table after using the filter and clearing the filter.

Edit 2:

I fixed the issue, by doing this Jank below in ngAfterViewInit() but I would still like a better answer, than this Jank I Just made up:

  ngAfterViewInit() {
    setTimeout(()=> {
      this.dataSource.filter = "p";
    }, 100);
    setTimeout(()=> {
      this.dataSource.filter = "";
    }, 1000);
  }


Solution 1:[1]

I don't think it has to do with the filter. You're probably not seeing data because your table is rendered with null data. And after you recieve data from onReadCollection you're not updating the dataSource.

async onReadCollection(userUID: string) {
     const snapIds = await this.db.collection(`users/${userUID}/stalking`).get().map(snap => snap?.id);
     for (const id of snapIds) {
       let user: User = await this.user.findUserbyUID(id);
       this.usersUserStalking.push(user);
     }
       this.dataSource.data = [...this.usersUserStalking] // update dataScource
}

Look over this part in general. Nesting HTTP calls and subscriptions are not good practice. Also, you're creating subscriptions recursively yet never unsubscribing them. You could use async/await pattern, but I would advise using other rxjs methods (like mergeMap) to work with async observables.

Solution 2:[2]

try use

        this.dataSource=new MatTableDataSource(this.usersUserStalking)
        this.dataSource.sort = this.sort;
        this.dataSource.paginator = this.paginator;

And use (input) not (keyup)

  <input matInput (input)="doFilter($event)" ..>

    changeFilter(value:any){
      this.dataSource.filter = value.trim().toLowerCase();
    }

Solution 3:[3]

It might have to do with the ngIf conditions. Since usersUserStalking part is async, you might want to create an observable in your ts file such as

$isVisible = new BehaviourSubject(false);

and after your this.usersUserStalking.push(<User>user) you can add this line: this.$isVisible.next(this.usersUserStalking.length > 0 && this.isLoading == false).

And in your html simply *ngIf="$isVisible | async";

Another small remark, I don't think you need ng-containers for ngIf, you can put your ngIf inside the component you use like this:

<mat-table *ngIf="$isVisible | async" [dataSource]="dataSource" matSort>

Solution 4:[4]

This issue happens because it triggers callback inside finalize() but won't wait for the async requests(to get user detail) inside subscribe(), so when assign this.usersUserStalking to dataSource, it is empty array at that time. To fix this, we can use some operators to stream line the requests, which can ensure finalize is called after all users are requested, try below:

async onReadCollection(userUID: string) {
    this.isLoading = true;
    this.db.collection(`users/${userUID}/stalking`).get()
      .pipe(
        finalize(() => {
          this.isLoading = false;
          this.dataSource.sort = this.sort;
          this.dataSource.paginator = this.paginator;
          console.log(this.usersUserStalking);
          this.dataSource.data = this.usersUserStalking; // update dataScource
          console.log(this.dataSource.data);
        }),
        switchMap(snaps => {
          const requests = snaps && snaps.length ? snaps.map(snap => {
            return this.user.findUserbyUID(snap.id);
          }): []
          return forkJoin(requests);
        })
      )
      .subscribe(users => {
        this.usersUserStalking.push(...users);
      });
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Eliseo
Solution 3 Can Geylan
Solution 4 Leon