I currently have a data table that is being populated with data coming from Firestore. I also used MatTableDataSource to implement pagination, sorting, and filtering. All 3 work fine but for some reason my data only loads once when the page is refreshed. If I go to another page and then back to the table the data is gone. I'm at a loss as to why this is happening. Below is my code.
Service
import { Injectable } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Account } from './../models/account.model';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class AccountService {
accountsCollection: AngularFirestoreCollection<Account>;
accounts: Observable<Account[]>;
constructor(public afs: AngularFirestore) {
this.accountsCollection = afs.collection('accounts');
this.accounts = this.accountsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Account;
data.id = a.payload.doc.id;
return data;
});
});
}
getAccounts() {
return this.accounts;
}
}
Component
import { Account } from './../../../models/account.model';
import { Component, ViewChild, OnInit } from '@angular/core';
import { MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
import { AccountService } from '../../../services/account.service';
import { AfterViewInit } from '@angular/core/src/metadata/lifecycle_hooks';
@Component({
selector: 'app-account-table',
templateUrl: './account-table.component.html',
styleUrls: ['./account-table.component.css']
})
export class AccountTableComponent implements AfterViewInit {
dataSource = new MatTableDataSource<Account>();
displayedColumns = [
'salesStep',
'status',
'idn',
'hospital',
'state',
'regionalManager',
'accountExecutive',
'clientLiaison',
'gpo'
];
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
constructor(private accountService: AccountService) {}
ngAfterViewInit() {
this.accountService.getAccounts().subscribe(data => {
this.dataSource.data = data;
console.log(this.dataSource.data);
});
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
applyFilter(filterValue: string) {
filterValue = filterValue.trim(); // Remove whitespace
filterValue = filterValue.toLowerCase(); // Datasource defaults to lowercase matches
this.dataSource.filter = filterValue;
}
}
HTML
<div class="example-header">
<mat-form-field>
<input matInput #filter (keyup)="applyFilter($event.target.value)" placeholder="Search">
</mat-form-field>
</div>
<mat-card class="example-container">
<mat-table #table [dataSource]="dataSource" matSort>
<!--- Note that these columns can be defined in any order.
The actual rendered columns are set as a property on the row definition" -->
<!-- Sales Step Column -->
<ng-container matColumnDef="salesStep">
<mat-header-cell *matHeaderCellDef mat-sort-header> Sales Step </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.salesStep}} </mat-cell>
</ng-container>
<!-- Status Column -->
<ng-container matColumnDef="status">
<mat-header-cell *matHeaderCellDef mat-sort-header> Status </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.status}} </mat-cell>
</ng-container>
<!-- IDN Column -->
<ng-container matColumnDef="idn">
<mat-header-cell *matHeaderCellDef mat-sort-header> IDN </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.idn}} </mat-cell>
</ng-container>
<!-- Hospital Column -->
<ng-container matColumnDef="hospital">
<mat-header-cell *matHeaderCellDef mat-sort-header> Hospital </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.hospital}} </mat-cell>
</ng-container>
<!-- State Column -->
<ng-container matColumnDef="state">
<mat-header-cell *matHeaderCellDef mat-sort-header> State </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.state}} </mat-cell>
</ng-container>
<!-- Regional Manager Column -->
<ng-container matColumnDef="regionalManager">
<mat-header-cell *matHeaderCellDef mat-sort-header> RM </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.regionalManager}} </mat-cell>
</ng-container>
<!-- Account Executive Column -->
<ng-container matColumnDef="accountExecutive">
<mat-header-cell *matHeaderCellDef mat-sort-header> AE </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.accountExecutive}} </mat-cell>
</ng-container>
<!-- Client Liaison Column -->
<ng-container matColumnDef="clientLiaison">
<mat-header-cell *matHeaderCellDef mat-sort-header> CL </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.clientLiaison}} </mat-cell>
</ng-container>
<!-- GPO Column -->
<ng-container matColumnDef="gpo">
<mat-header-cell *matHeaderCellDef mat-sort-header> GPO </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.gpo}} </mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;">
</mat-row>
</mat-table>
<!-- <div class="example-no-results"
[style.display]="(accountService.accounts | async)?.length">
No accounts found matching filter.
</div> -->
<mat-paginator #paginator
[pageSize]="10"
[pageSizeOptions]="[5, 10, 20]">
</mat-paginator>
</mat-card>
This may work better for the getAccounts method:
getAccountsX() {
return this.afs.collection<Account[]>('accounts').snapshotChanges().map((accounts) => {
return accounts.map(a => {
const data = a.payload.doc.data() as Account;
const id = a.payload.doc.id;
return { id, ...data }
});
});
}
I have never tried making a firestore call in the constructor of the service but always make the database calls in a method that gets called during the ngOnInit in my component.
So in the component you could have a object accounts: Observable<Account[]>
that is of type Observable<Account[]>
and set it to equal getAccountsX(). Then in your markup I would *ngIf the entire table like this: *ngIf="(accounts | async) as acts"
. Then the dataSource would actually be acts
. I have never used the DataTable yet but this is just an approach I would take to try and keep the subscription to the data active. If you want I can EDIT your question with this.
EDIT:
Here is an explanation of two separate ways to handle that subscription:
So here in my Component I am fetching the Observable and then also subscribing to it to save the array of whatever data model you are fetching:
accountsObservable: Observable<Account[]>;
accountsArray: Account[];
constructor(
private ds: DatabaseService
) {}
ngOnInit() {
this.accountsObservable = this.ds.getAccountsX();
this.accountsObservable.subscribe(accounts => {
this.accountsArray = accounts;
});
}
Then here in my markup you can create the subscription using *ngFor and the ASYNC pipe, or simply loop through the array after it has been acknowledged by the subscription:
<!-- This div below is subscribing to the Observable in markup using the 'async' pipe to make sure it waits for data -->
<div id="markup-subscription" *ngFor="let act of (accountsObservable | async)">
<p>{{ act?.id }}</p>
</div>
<!-- This div below is looping through the array that was pulled from the subscription of the Observable -->
<div id="component-subscription" *ngFor="let act of accountsArray">
<p>{{ act?.id }}</p>
</div>
One reason for waiting for the subscription in the Component code is if there is a need to manipulate the data before spitting it out on the UI. I believe if you are using the second option of subscribing in the Component code instead of your markup you would want to make sure the *ngFor isn't trying to loop through an empty Array as the subscription may not have set the array before the content wants to load on the DOM. So I would *ngIf
the accountsArray to make sure it is set first like so:
<div id="component-subscription" *ngIf="accountsArray" *ngFor="let act of accountsArray">
Granted this is not using the MatDataTable as I wanted to show an example of how these subscriptions work and the goal is to use one subscription
In regards to unsubscribing, the reason that is not an option is because you must set the Observable subscription to a variable like so:
const subscription = this.accountsObservable.subscribe(accounts => {
this.accountsArray = accounts;
});
subscription.unsubscribe();
I hope this can help explain the state of the subscription as you are looping through the collection or document in the UI.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With