Angular Material 2 Data Table Connect to AngularFire2 or Firebase?

I'm sorry that it was just plug and play :-) I talked with this watch, none of my little experiments worked. The md data table is new, so there is no divine knowledge on the Internet yet. Finding a good way to connect Firebase to your desk would be a good start. Any ideas?

I currently have this setting. My code works fine without a table with a standard installation and Angular code using ngFor and creating a list from a template. Thus, the code delivers data from Firebase using AngularFire 2. The problem with the new md data table is a problem.

At first, the template will not be displayed. I know that I configured NgModule correctly, so I suspect that the data source is not connecting and is not generating this error. This is a bug 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 search.component.html members look identical to the official docs, except that 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 the following 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 dumped these data table functions into my working code in members-search.service.ts and used the same code in connect () that I used elsewhere in 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 and plunker documents create the data source and database in the component, but it seems that most of this is not needed if I already have Firebase. I study all this, so I am far from an expert in everything and maybe I missed something.

If you have not entered this new setting before, then here are the documents. The md table is built on top of the cdk table to create the cdk table.

https://material.angular.io/components/table/overview

https://material.angular.io/guide/cdk-table

+7
angular angular-material2
source share
3 answers

I added code to connect to Firebase when using MD Paginator for an MD data table. Paginator makes the code in the service more complex. Most of the code is in the service where it belongs. Enjoy it!

members of admin.service.ts

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

all-members.component.ts

Was there refactoring in ngOnInit and 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; }); } 

all-members.component.html

Please note: I have buttons in the lines for deleting and editing, and they work fine. The trick is that you need the database key in the 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> 
+8
source share

The following solution works. It took a while to figure out how to resolve this issue, and I had expert help from Will Howell in the Reddit Angular group. My service is more concerned with CRUD materials, but they have not been baked yet. I configure this for a master part with buttons to show deletion and editing. The final column brings the Firebase $key to the DOM, which I grab and use to access the CRUD functions in the component and service. When I find out, I will post a complete mess, error, code in another post with a more specific name.

members of admin.service.ts

A service includes three classes if they are configured like AM2 data table documents, as I already did. I'm not sure that I like it, but now I will follow the documents.

 import { AngularFireDatabase, FirebaseListObservable } from 'angularfire2/database'; import { FirebaseApp } from 'angularfire2'; import { Inject, Injectable } from '@angular/core'; import { Member } from './member-admin.model'; import { SuccessService } from '../../../shared/success.service'; import { DataSource } from '@angular/cdk'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; @Injectable() export class MembersAdminService { private members$: FirebaseListObservable<Member[]>; constructor( private af: AngularFireDatabase, private successService: SuccessService, @Inject(FirebaseApp) fb) { this.members$ = af.list('Members'); } // CRUD stuff here in this class... // *** 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)); } } export class MembersAdminSource extends DataSource<Member> { constructor(private members: Member[]) { super(); } /** Connect function called by the table to retrieve one stream containing the data to render. */ connect(): Observable<Member[]> { return Observable.of(this.members); } disconnect() {} } 

all-members.component.ts

 import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { MembersAdminService } from './member-admin.service'; import { MembersAdminSource } from './member-admin.service'; import { ConfirmService } from '../../../shared/confirm.service'; import { Member } from './member-admin.model'; @Component({ selector: 'app-all-members', templateUrl: './all-members.component.html' }) export class AllMembersComponent implements OnInit { members: Member[]; private selectedId: number; private result: boolean; allMembers: Member[]; // For MD data table. // private dataSource: DataSource<any>; private dataSource: MembersAdminSource | null; private displayedColumns = [ 'firstName', 'lastName', 'mainSkillTitle', 'mainSkills', 'delete', 'edit', 'key' ]; constructor( private membersAdminService: MembersAdminService, private router: Router, private confirmService: ConfirmService ) {} ngOnInit() { // This was the code for an *ngFor setup before installing the data table. /* this.membersAdminService.getMembers() .subscribe( members => this.allMembers = this.members = members ); */ this.membersAdminService.getMembers() .subscribe(members => { this.members = members; this.dataSource = new MembersAdminSource(members); }); } 

all-members.component.html

 <md-table #table [dataSource]="dataSource"> <!-- First Name Column --> <ng-container cdkColumnDef="firstName"> <md-header-cell *cdkHeaderCellDef> 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> 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 </md-header-cell> <md-cell *cdkCellDef="let row"> <button (click)="deleteMember(member)">Delete</button> </md-cell> </ng-container> <!-- Edit button Column --> <ng-container cdkColumnDef="edit"> <md-header-cell *cdkHeaderCellDef> Edit </md-header-cell> <md-cell *cdkCellDef="let row"> <button class="badge" (click)="goToDetailPage(member)">Edit</button> </md-cell> </ng-container> <!-- 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> 
+3
source share

I think I would add my approach to anyone looking for this solution.

I tried to make it reusable in collections. It supports data retrieval and sorting up and down by specified fields. Remember to add sort fields to .indexOn in firebase rules.

I was unable to get the paging to work because it is too hard to work out startKey!

firebase-datasource.ts

Let's start by defining a template data source that I can reuse for all collections that require it.

 import { Component } from '@angular/core'; import { DataSource } from '@angular/cdk/collections'; import { Observable } from 'rxjs/Observable'; import { Subscription } from 'rxjs/Subscription'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { AngularFireDatabase } from 'angularfire2/database'; /** * Sortable Interface - Used for specifying the sort order. */ export interface Sort { field: string; direction: '' | 'asc' | 'desc'; } /** * FirebaseDataSource is a templated datasource for Firebase. At this stage it * allows: * * Tracking data updates to the underlying datarecords. * * Sorting ascending and descending * * We have not implemented paging controls as its too difficult with NoSQL. It also * does not support multi-field sorting. */ export class FirebaseDataSource<T> extends DataSource<T> { /** * The datachange subscriber emits new data when the Firebase records are updated. */ dataChange: BehaviorSubject<T[]> = new BehaviorSubject<T[]>([]); /** * The sort change is updated when the sort order is changed. */ sortChange: BehaviorSubject<Sort> = new BehaviorSubject<Sort>({field: '', direction: ''}); /** * Path tracks the path of the list of records. eg /items */ path: string; /** * Keep for cleaning up the subscription */ private _sub: Subscription; /** * Getters and setters for setting sort order. */ get sort(): Sort { return this.sortChange.value; } set sort(sort: Sort) { this.sortChange.next(sort); } /** * Construct an instance of the datasource. * * @param path The Firebase Path eg /items * @param db Injectable AngularFireDatabase * @param sort Optional initial sort order for the list. */ constructor( path: string, protected db: AngularFireDatabase, sort?: Sort) { super(); this.path = path; /** * Sets up a subscriber to the path and emits data change events. */ this._sub = this.db.list(this.path).valueChanges<T>() .subscribe((data) => { this.dataChange.next(data); }); if (sort) { this.sort = sort; } } /** * Connect to the data source, retrieve initial data, and observe changes. * It tracks changes to either the underlying data, or to the sort order and remaps * the query. * * @returns Observable<T[]> */ connect(): Observable<T[]> { const dataChanges = [ this.dataChange, this.sortChange ]; const _that = this; return Observable.merge(...dataChanges) .switchMap(() => { if (_that.sort.field !== '' && _that.sort.direction !== '') { return this.db.list(this.path, ref => ref.orderByChild(this.sort.field)).valueChanges<T>() .map((data: T[]) => { if (_that.sort.direction === 'desc') { return data.reverse(); } else { return data; } }); } else { return this.db.list(this.path).valueChanges<T>(); } }); } /** * Cleans up the open subscription. */ disconnect() { this._sub.unsubscribe(); } } 

Then an example of use:

Role-playing datasource.ts Declare this in the "providers" of the corresponding module. (code not shown)

 import { FirebaseDataSource } from '../../shared/firebase-datasource'; import { Role } from './role'; import { Injectable } from '@angular/core'; import { AngularFireDatabase } from 'angularfire2/database'; @Injectable() export class RoleDataSource extends FirebaseDataSource<Role> { constructor( protected db: AngularFireDatabase ) { super('/roles', db); } } 

Now look at the user interface component:

all-roles.component.html

Ignoring external code for toolbars, etc. Important parts for notification are the mat-table and matSort .

 <!-- Toolbar --> <div class="plr20 mb10 bb-light"> <div fxLayout="row" fxLayoutAlign="space-between center"> <h1>All Roles</h1> <div> <a mat-button [routerLink]="['/roles', 'new']"> <mat-icon class="cursor-pointer">add</mat-icon>New Role</a> </div> </div> </div> <! -- End Toolbar --> <div class="plr20" fxLayout="column"> <div *ngIf="contentLoading" fxLayout="row" fxLayoutAlign="center"> <div class="spinner-container"> <mat-spinner diameter="48" strokeWidth="4"></mat-spinner> </div> </div> <mat-card class="mb20"> <mat-card-content> <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" --> <!-- Identifier Column --> <ng-container matColumnDef="identifier"> <mat-header-cell *matHeaderCellDef mat-sort-header>Identifier</mat-header-cell> <mat-cell *matCellDef="let role"> {{role.identifier}} </mat-cell> </ng-container> <!-- Title Column --> <ng-container matColumnDef="title"> <mat-header-cell *matHeaderCellDef mat-sort-header> Title </mat-header-cell> <mat-cell *matCellDef="let role"> {{role.title}} </mat-cell> </ng-container> <!-- Last Updated --> <ng-container matColumnDef="lastUpdated"> <mat-header-cell *matHeaderCellDef mat-sort-header> Last Updated </mat-header-cell> <mat-cell *matCellDef="let role"> {{role.lastUpdated | date}} {{role.lastUpdated | date: 'mediumTime'}} </mat-cell> </ng-container> <!-- Actions --> <ng-container matColumnDef="actions"> <mat-header-cell *matHeaderCellDef> Actions </mat-header-cell> <mat-cell *matCellDef="let role"> <a mat-button [routerLink]="['/roles/', role.identifier]">View</a> </mat-cell> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> </mat-table> </mat-card-content> </mat-card> </div> 

all-roles.component.ts

Finally, a user interface level implementation. It captures matSort updates to its taste and emits them to the data source, because I don't like to bind MatSort directly to the data source layer. I also added a simple Ajax loader during data loading.

 import { Component, OnDestroy, ViewChild, OnInit } from '@angular/core'; import { AngularFireDatabase, AngularFireList } from 'angularfire2/database'; import { Observable } from 'rxjs/Observable'; import { Subscription } from 'rxjs/Subscription'; import { Role } from './role'; import { RoleDataSource } from './role-datasource'; import { MatSort } from '@angular/material'; @Component({ templateUrl: './all-roles.component.html', styles: [':host {width: 100% }'] }) export class AllRolesComponent implements OnDestroy, OnInit { roles: Observable<any>; contentLoading: boolean; subs: Subscription[] = []; displayedColumns = ['identifier', 'title', 'lastUpdated', 'actions']; @ViewChild(MatSort) sort: MatSort; constructor(private db: AngularFireDatabase, private dataSource: RoleDataSource) { this.contentLoading = true; } ngOnInit() { const _that = this; // simply handles hiding the AJAX Loader this.dataSource.connect().take(1).subscribe(data => { this.contentLoading = false; }); this.subs.push(this.sort.sortChange.subscribe(() => { _that.dataSource.sort = { field: _that.sort.active, direction: _that.sort.direction }; })); } ngOnDestroy() { this.subs.forEach((sub) => { sub.unsubscribe(); }); } } 
0
source share

All Articles