typescript 带 Firestore 的角材质 MatTableDataSource

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/47261190/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 05:01:48  来源:igfitidea点击:

Angular Material MatTableDataSource with Firestore

angulartypescriptangular-material2google-cloud-firestore

提问by Kyle

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.

我目前有一个数据表,其中填充了来自 Firestore 的数据。我还使用 MatTableDataSource 来实现分页、排序和过滤。所有 3 个都可以正常工作,但由于某种原因,我的数据仅在页面刷新时加载一次。如果我转到另一个页面,然后返回到表格,数据就会消失。我不知道为什么会这样。下面是我的代码。

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

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>

采纳答案by Nicholas Pesa

This may work better for the getAccounts method:

这对于 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 }
      });
    });
  }

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.

我从未尝试过在服务的构造函数中进行 firestore 调用,但总是在我的组件中的 ngOnInit 期间调用的方法中进行数据库调用。

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.

因此,在组件中,您可以拥有一个accounts: Observable<Account[]>类型的对象Observable<Account[]>并将其设置为等于 getAccountsX()。然后,在您的标记我会* ngIf整个表是这样的:*ngIf="(accounts | async) as acts"。那么 dataSource 实际上是acts. 我从未使用过 DataTable,但这只是我尝试保持对数据的订阅处于活动状态的一种方法。如果你愿意,我可以用这个编辑你的问题。

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:

因此,在我的组件中,我正在获取 Observable,然后订阅它以保存您正在获取的任何数据模型的数组:

  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:

然后在我的标记中,您可以使用 *ngFor 和 ASYNC 管道创建订阅,或者在订阅确认后简单地循环遍历数组:

<!-- 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 *ngIfthe accountsArray to make sure it is set first like so:

在组件代码中等待订阅的一个原因是,是否需要在将数据吐出到 UI 之前对其进行操作。我相信如果您在组件代码中使用订阅的第二个选项而不是您的标记,您会想要确保 *ngFor 不会尝试遍历空数组,因为订阅可能没有在内容之前设置数组想要加载 DOM。所以我会*ngIf使用accountsArray来确保它首先像这样设置:

<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

授予这不是使用 MatDataTable 因为我想展示这些订阅如何工作的示例,目标是使用一个订阅

In regards to unsubscribing, the reason that is not an option is because you must set the Observable subscription to a variable like so:

关于取消订阅,不是一个选项的原因是因为您必须将 Observable 订阅设置为一个变量,如下所示:

    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.

我希望这可以帮助您在 UI 中循环浏览集合或文档时解释订阅的状态。

回答by Joshua Fabillar

try the simplest brother. here.

试试最简单的兄弟。这里。

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

hope this helps.

希望这可以帮助。

回答by funkizer

Save the subscription you get from getAccounts().subscribe and call unsubscribe() to it in ngOnDestroy. I didn't test but it might help if af2 caches the subscriptions, as the observable never completes on it's own. Necessary and good practice for avoiding memory leaks anyway.

保存从 getAccounts().subscribe 获得的订阅并在 ngOnDestroy 中调用 unsubscribe() 到它。我没有测试,但如果 af2 缓存订阅可能会有所帮助,因为 observable 永远不会自行完成。无论如何避免内存泄漏的必要和良好实践。