Угловой материал MatTableDataSource с Firestore
В настоящее время у меня есть таблица данных, которая заполняется данными, поступающими из Firestore. Я также использовал MatTableDataSource для реализации разбиения на страницы, сортировки и фильтрации. Все 3 работают нормально, но по какой-то причине мои данные загружаются только один раз при обновлении страницы. Если я перейду на другую страницу, а затем обратно в таблицу, данные исчезнут. Я не понимаю, почему это происходит. Ниже мой код.
сервис
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;
}
}
компонент
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>
3 ответов
это может работать лучше для метода 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 }
});
});
}
Я никогда не пытался сделать вызов firestore в конструкторе службы, но всегда делаю вызовы базы данных в методе, который вызывается во время ngOnInit в моем компоненте.
таким образом, в компоненте у вас может быть объект accounts: Observable<Account[]>
это типа Observable<Account[]>
и установите его равным getAccountsX (). Тогда в вашей разметке я бы * ngIf всю таблицу так:*ngIf="(accounts | async) as acts"
. Тогда источник будет фактически acts
. Я еще никогда не использовал DataTable, но это просто подход, который я бы попытался сохранить подписку на данные активными. Если хотите, я могу отредактировать ваш вопрос с помощью этого.
EDIT:
вот объяснение двух отдельных способов обработки этой подписки:
Итак, здесь, в моем компоненте, я получаю наблюдаемое, а затем подписываюсь на него, чтобы сохранить массив любой модели данных, которой вы являетесь fetching:
accountsObservable: Observable<Account[]>;
accountsArray: Account[];
constructor(
private ds: DatabaseService
) {}
ngOnInit() {
this.accountsObservable = this.ds.getAccountsX();
this.accountsObservable.subscribe(accounts => {
this.accountsArray = accounts;
});
}
затем здесь, в моей разметке, вы можете создать подписку с помощью *ngFor и асинхронного канала или просто выполнить цикл через массив после того, как он был подтвержден подпиской:
<!-- 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>
одна из причин ожидания подписки в коде компонента заключается в том, что необходимо манипулировать данными, прежде чем выплевывать их в пользовательский интерфейс. Я считаю, что если вы используете второй вариант подписки в коде компонента вместо вашей разметки, вы хотел бы убедиться, что *ngFor не пытается выполнить цикл через пустой массив, поскольку подписка, возможно, не установила массив до того, как содержимое захочет загрузить на DOM. Так что я бы *ngIf
accountsArray, чтобы убедиться, что он установлен первым, как так:
<div id="component-subscription" *ngIf="accountsArray" *ngFor="let act of accountsArray">
конечно, это не использует MatDataTable, как я хотел показать пример того, как эти подписки работают, и цель состоит в том, чтобы использовать одну подписку
что касается отмены подписки, причина, которая не является опция, потому что вы должны установить наблюдаемую подписку на переменную, например:
const subscription = this.accountsObservable.subscribe(accounts => {
this.accountsArray = accounts;
});
subscription.unsubscribe();
Я надеюсь, что это может помочь объяснить состояние подписки, когда вы просматриваете коллекцию или документ в пользовательском интерфейсе.
сохраните подписку, которую вы получаете от getAccounts ().подпишитесь и позвоните unsubscribe () на него в ngOnDestroy. Я не тестировал, но это может помочь, если af2 кэширует подписки, поскольку наблюдаемый никогда не завершается самостоятельно. Необходимая и хорошая практика для предотвращения утечек памяти в любом случае.
попробуйте самый простой брат. здесь.
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;
});
});
}
надеюсь, что это помогает.