typescript - Angular Material MatTableDataSource med Firestore


Jag har för närvarande en datatabell som fylls med data från Firestore. Jag har också använt MatTableDataSource för att implementera pagination, sortering och filtrering. Alla tre fungerar bra, men av någon anledning laddas mina data bara en gång när sidan uppdateras. Om jag går till en annan sida och sedan tillbaka till tabellen är datan borta. Jag förstår inte varför detta händer. Nedan är min kod.

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;
  }

}

Komponent

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>

Svar #1

Detta kan fungera bättre för metoden getAccounts:

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

Jag har aldrig försökt göra ett Firestore-anrop i konstruktorn för tjänsten men gör alltid databas-anropen i en metod som anropas under ngOnInit i min komponent.

Så i komponenten skulle du kunna ha ett objekt accounts: Observable<Account[]> som är av typen Observable<Account[]> och sätta det lika med getAccountsX(). Sedan i din markup skulle jag *ngIf hela tabellen så här: *ngIf="(accounts | async) as acts". Då skulle dataSource faktiskt vara acts. Jag har aldrig använt DataTable än, men detta är bara ett tillvägagångssätt jag skulle ta för att försöka hålla prenumerationen på datan aktiv. Om du vill kan jag REDIGERA din fråga med detta.

REDIGERA :

Här är en förklaring av två separata sätt att hantera den prenumerationen:

Så här i min komponent hämtar jag Observable och prenumererar sedan också på den för att spara arrayen av vilken datamodell som helst som du hämtar:

  accountsObservable: Observable<Account[]>;
  accountsArray: Account[];

  constructor(
    private ds: DatabaseService
  ) {}

  ngOnInit() {
    this.accountsObservable = this.ds.getAccountsX();

    this.accountsObservable.subscribe(accounts => {
      this.accountsArray = accounts;
    });
  }

Då här i min markup kan du skapa prenumerationen med *ngFor och ASYNC-pipen, eller helt enkelt loopa genom arrayen efter att den har bekräftats av prenumerationen:

<!-- 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>

En anledning till att vänta med prenumerationen i komponentkoden är om det finns ett behov av att manipulera data innan den visas i användargränssnittet. Jag tror att om du använder det andra alternativet att prenumerera i komponentkoden istället för i din markering, vill du säkerställa att *ngFor inte försöker loopa genom en tom array eftersom prenumerationen kanske inte har satt arrayen innan innehållet vill laddas i DOM. Så jag skulle använda *ngIf på accountsArray för att säkerställa att den är satt först, på följande sätt:

<div id="component-subscription" *ngIf="accountsArray" *ngFor="let act of accountsArray">

Visserligen använder detta inte MatDataTable eftersom jag ville visa ett exempel på hur dessa prenumerationer fungerar och målet är att använda en prenumeration

När det gäller att avsluta prenumerationen, är anledningen till att det inte är ett alternativ att du måste tilldela Observable-prenumerationen till en variabel på följande sätt:

    const subscription = this.accountsObservable.subscribe(accounts => {
      this.accountsArray = accounts;
    });

    subscription.unsubscribe();

Jag hoppas att detta kan hjälpa till att förklara statusen för prenumerationen när du loopar genom samlingen eller dokumentet i användargränssnittet.

Svar #2

prova den enklaste brodern. här.

constructor(public afs: AngularFirestore) {
    this.accountsCollection = afs.collection('accounts');
}

getAccounts() {
    return 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;
      });
    });
  }

hoppas detta hjälper.

Relaterade artiklar