I wish it was just plug and play :-) I've been thrashing around with this for hours with none of my little experiments working. The md data table is new, so there is almost no divine knowledge on the Web yet. Finding a good way to connect Firebase to the table would be a good start. Any ideas?
Currently I have this setup. My code works great without the table with the standard Angular setup and code, using ngFor and creating a list from a template. So the code delivers the data from Firebase with AngularFire 2. Trying out the new md data table is the problem.
First, the template won't render. I know I have NgModule setup correctly so my suspicion is that the data source isn't connecting and creating this error. This is the error in the Chrome console.
Template parse errors:
Can't bind to 'dataSource' since it isn't a known property of 'md-table'.
1. If 'md-table' is an Angular component and it has 'dataSource' input, then verify that it is part of this module.
My members-search.component.html looks identical to the official docs except I changed the template binding:
<md-table #table [dataSource]="dataSource">
<ng-container cdkColumnDef="memberName">
<md-header-cell *cdkHeaderCellDef> Name </md-header-cell>
<md-cell *cdkCellDef="let row"> {{member.firstName}} {{ member?.lastName }} </md-cell>
</ng-container>
members-search.component.ts has these relevant parts:
import { DataSource } from '@angular/cdk';
@Injectable()
export class MembersAdminService {
private members$: FirebaseListObservable<Member[]>;
private dataSource: DataSource<any>;
constructor(
private af: AngularFireDatabase,
@Inject(FirebaseApp) fb) {
this.members$ = af.list('Members');
}
And I dropped these data table functions into my working code in members-search.service.ts and used the same code in the connect() that I've been using elsewhere on this service.
// md table dataSource functions.
public connect(): FirebaseListObservable<any> {
return this.af.list('Members', {
query: {
orderByChild: 'lastName'
}
});
// return this._exampleDatabase.dataChange;
}
public disconnect() {}
The data table docs and plunker create a data source and database in the component but it seems that most of that isn't necessary if I already have Firebase. I'm learning all this so I'm far from an expert at anything and maybe I'm missing something.
If you haven't got into this new setup before then here are the docs. The md table is built on top of the cdk table to give the cdk table the styling.
https://material.angular.io/components/table/overview
https://material.angular.io/guide/cdk-table
I've added the code for connecting to Firebase when using the MD Paginator for the MD Data Table. The Paginator makes the code in the service more complicated. Most of the code is in the service where it belongs. Enjoy!
import { AngularFireDatabase, FirebaseListObservable } from 'angularfire2/database';
import { FirebaseApp } from 'angularfire2';
import { Inject, Injectable } from '@angular/core';
import { MemberModel } from './member-admin.model';
import { SuccessService } from '../../../shared/success.service';
// Data Table imports.
import { MdPaginator } from '@angular/material';
import { DataSource } from '@angular/cdk';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/combineLatest';
import 'rxjs/add/operator/map';
@Injectable()
export class MembersAdminService {
private membersData$: FirebaseListObservable<MemberModel[]>;
constructor(
public af: AngularFireDatabase,
private successService: SuccessService,
// For Create and Update functions.
@Inject(FirebaseApp) fb) {
this.membersData$ = af.list('Members');
}
// ... CRUD stuff not relevant to the MD Table ...
// *** MD DATA TABLE SERVICES. ***
@Injectable()
export class MemberDatabase {
/* Stream that emits whenever the data has been modified. */
public dataChange: BehaviorSubject<MemberModel[]> = new BehaviorSubject<MemberModel[]>([]);
get data(): MemberModel[] {
return this.dataChange.value; }
// Connection to remote db.
private database = this.memberAdminService.af.list('Members', {
query: {
orderByChild: 'lastName'
}
});
public getMembers(): FirebaseListObservable<MemberModel[]> {
return this.database;
}
constructor(private memberAdminService: MembersAdminService) {
this.getMembers()
.subscribe(data => this.dataChange.next(data));
}
}
@Injectable()
export class MembersAdminSource extends DataSource<MemberModel> {
constructor(
private memberDatabase: MemberDatabase,
private paginator: MdPaginator) {
super();
}
/** Connect function called by the table to retrieve one stream containing the data to render. */
connect(): Observable<MemberModel[]> {
const displayDataChanges = [
this.memberDatabase.dataChange,
this.paginator.page,
];
return Observable
.merge(...displayDataChanges) // Convert object to array with spread syntax.
.map(() => {
const dataSlice = this.memberDatabase.data.slice(); // Data removed from viewed page.
// Get the page's slice per pageSize setting.
const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
const dataLength = this.paginator.length; // This is for the counter on the DOM.
return dataSlice.splice(startIndex, this.paginator.pageSize);
});
}
disconnect() {}
}
Did some refactoring in ngOnInit
and the class properties.
import { Component, OnInit, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { Subject } from 'rxjs/Subject';
// For MD Data Table.
import { MdPaginator } from '@angular/material';
import { MembersAdminService, MembersAdminSource, MemberDatabase } from './member-admin.service';
import { ConfirmService } from '../../../shared/confirm.service';
import { MemberModel } from './member-admin.model';
@Component({
selector: 'app-all-members',
templateUrl: './all-members.component.html'
})
export class AllMembersComponent implements OnInit {
membersData: MemberModel[];
private result: boolean;
allMembers: MemberModel[];
// For search
startAt = new Subject();
endAt = new Subject();
lastKeypress: 0;
// For MD data table.
// private memberDatabase = new MemberDatabase(); // Requires a param but? Moved to constructor.
private dataSource: MembersAdminSource | null;
private displayedColumns = [
'firstName',
'lastName',
'mainSkillTitle',
'mainSkills',
'delete',
'key'
];
@ViewChild(MdPaginator)
paginator: MdPaginator;
public dataLength: any; // For member counter on DOM.
constructor(
private membersAdminService: MembersAdminService,
private memberDatabase: MemberDatabase,
private router: Router,
private confirmService: ConfirmService
) {}
ngOnInit() {
this.memberDatabase.getMembers()
.subscribe(members => {
this.dataSource = new MembersAdminSource(this.memberDatabase, this.paginator);
this.dataLength = members;
});
}
Notice I have buttons in the rows for delete and edit and they work fine. The trick is you need the database key in a hidden column.
<md-table #table [dataSource]="dataSource">
<!-- First Name Column -->
<ng-container cdkColumnDef="firstName">
<md-header-cell *cdkHeaderCellDef> First Name </md-header-cell>
<md-cell *cdkCellDef="let row"> {{row.firstName}} </md-cell>
</ng-container>
<!-- Las Name Column -->
<ng-container cdkColumnDef="lastName">
<md-header-cell *cdkHeaderCellDef> Last Name </md-header-cell>
<md-cell *cdkCellDef="let row"> {{row.lastName}} </md-cell>
</ng-container>
<!-- Title Column -->
<ng-container cdkColumnDef="mainSkillTitle">
<md-header-cell *cdkHeaderCellDef> Title </md-header-cell>
<md-cell *cdkCellDef="let row"> {{row.mainSkillTitle}} </md-cell>
</ng-container>
<!-- Main Skills Column -->
<ng-container cdkColumnDef="mainSkills">
<md-header-cell *cdkHeaderCellDef> Main Skills </md-header-cell>
<md-cell *cdkCellDef="let row"> {{row.mainSkills}} </md-cell>
</ng-container>
<!-- Delete Buttons Column -->
<ng-container cdkColumnDef="delete">
<md-header-cell *cdkHeaderCellDef> Delete / Edit </md-header-cell>
<md-cell *cdkCellDef="let row">
<button (click)="deleteMember(row.$key)">Delete</button>
<button (click)="goToDetailPage(row.$key)">Edit</button>
</md-cell>
</ng-container>
<!-- Database key Column -->
<ng-container cdkColumnDef="key">
<md-header-cell *cdkHeaderCellDef class="hiddenField"> Key </md-header-cell>
<md-cell *cdkCellDef="let row" class="hiddenField"> {{row.$key}} </md-cell>
</ng-container>
<md-header-row *cdkHeaderRowDef="displayedColumns"></md-header-row>
<md-row *cdkRowDef="let row; columns: displayedColumns;"></md-row>
</md-table>
<md-paginator #paginator
[length]="dataLength?.length"
[pageIndex]="0"
[pageSize]="5"
[pageSizeOptions]="[5, 10, 25, 100]">
</md-paginator>
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