2

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);
  }
nuccio
  • 316
  • 6
  • 17
  • May I know what happens if we make the `filter` assignment conditional (in `doEvent` method) like so: `if (filterValue.trim().length > 0) { this.dataSource.filter = filterValue.trim().toLowerCase();} else { delete this.dataSource.filter; }` – jsN00b Apr 29 '22 at 04:07
  • 1
    can you create a stackblitz? – Robin Dijkhof Apr 29 '22 at 20:37
  • 1
    @jsN00b It still had the same result without any additional problems. – nuccio Apr 30 '22 at 17:00
  • @ Robin Dijkhof I will try to create a Stackblitz if I have time, my wife and I have a new baby, and I try to give her as much of a break as possible on the weekends. Sorry for the inconvenience. – nuccio Apr 30 '22 at 17:01
  • 1
    1. Check component strategy. 2 Check if you're assigning dataSource in Angular zone – yurzui May 02 '22 at 04:53
  • Please add a [stackblitz](https://stackblitz.com/edit/angular-ivy-vl1v45) example showing your issue. – luiscla27 May 03 '22 at 17:48
  • is it consoling the right data? – Can Geylan May 04 '22 at 12:51

4 Answers4

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.

Joosep Parts
  • 5,372
  • 2
  • 8
  • 33
  • I update the data in ngAfterViewInit(), but I got the table to start with one user now, but still have the same issue, I will update my code above. – nuccio Apr 25 '22 at 03:43
0

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();
    }
Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • Sorry this made it worse, but I fixed the functionality by adding timeouts to ngAfterViewInit(), but I would still like a better answer than that Jank, I just made up. – nuccio Apr 29 '22 at 03:37
0

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>

Can Geylan
  • 266
  • 1
  • 6
0

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);
      });
  }
}
Leon
  • 131
  • 4