web-dev-qa-db-fra.com

Matière angulaire MatTableDataSource avec Firestore

J'ai actuellement une table de données qui est remplie avec des données provenant de Firestore. J'ai également utilisé MatTableDataSource pour implémenter la pagination, le tri et le filtrage. Tous les 3 fonctionnent bien, mais pour une raison quelconque, mes données ne sont chargées qu'une fois lorsque la page est actualisée. Si je vais à une autre page puis à la table, les données sont parties. Je ne comprends pas pourquoi cela se produit. Ci-dessous mon code.

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

}

Composant

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

Cela peut fonctionner mieux pour la méthode 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 }
      });
    });
  }

Je n'ai jamais essayé d'appeler le constructeur du service dans un magasin Firestore, mais vous appelez toujours la base de données avec une méthode appelée lors du ngOnInit de mon composant. 

Ainsi, dans le composant, vous pouvez avoir un objet accounts: Observable<Account[]> de type Observable<Account[]> et le définir pour qu'il soit égal à getAccountsX (). Ensuite, dans votre balisage, je voudrais * ngIf si tout le tableau ressemble à ceci: *ngIf="(accounts | async) as acts". Ensuite, la source de données serait en réalité acts. Je n'ai encore jamais utilisé DataTable, mais il ne s'agit que d'une approche que je prendrais pour essayer de garder l'abonnement aux données actif. Si vous voulez, je peux éditer votre question avec ceci.

MODIFIER: 

Voici une explication de deux manières différentes de gérer cet abonnement:

Donc ici, dans mon composant, je vais chercher l'Observable et je m'y abonne également pour sauvegarder le tableau de n'importe quel modèle de données que vous allez chercher:

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

  constructor(
    private ds: DatabaseService
  ) {}

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

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

Ensuite, ici dans mon balisage, vous pouvez créer l'abonnement à l'aide de * ngFor et du canal ASYNC, ou simplement parcourir le tableau après l'avoir reconnu par l'abonnement:

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

Une des raisons d'attendre l'abonnement dans le code de composant est s'il est nécessaire de manipuler les données avant de les cracher sur l'interface utilisateur. Je crois que si vous utilisez la deuxième option de souscription dans le code de composant à la place de votre balise, vous voulez vous assurer que le * ngFor n'essaie pas de parcourir en boucle un tableau vide, car l'abonnement n'a peut-être pas défini le tableau avant le contenu. veut charger sur le DOM. Donc, je voudrais *ngIf le accountsArray pour m'assurer qu'il est réglé en premier comme ceci:

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

Cela dit, n'utilisez pas MatDataTable car je voulais montrer un exemple du fonctionnement de ces abonnements et l'objectif est d'utiliser un seul abonnement.

En ce qui concerne la désinscription, la raison pour laquelle ce n'est pas une option est que vous devez définir l'abonnement Observable sur une variable comme ceci:

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

    subscription.unsubscribe();

J'espère que cela peut aider à expliquer l'état de l'abonnement lorsque vous parcourez la collection ou le document dans l'interface utilisateur. 

3
Nicholas Pesa

essayez le frère le plus simple. ici. 

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

j'espère que cela t'aides.

1
Joshua Fabillar

Enregistrez l'abonnement que vous obtenez de getAccounts (). Subscribe et appelez unsubscribe () dans ngOnDestroy. Je n'ai pas testé, mais cela pourrait aider si af2 met en cache les abonnements, car l'observable ne se termine jamais d'elle-même. Nécessaire et bonne pratique pour éviter les fuites de mémoire.

0
funkizer