我希望它只是即插即用:-)我几个小时都在喋喋不休,没有我的小实验工作。 md数据表是新的,因此Web上几乎没有神圣的知识。找到将Firebase连接到桌面的好方法将是一个良好的开端。有什么想法吗?
目前我有这个设置。我的代码很好,没有带有标准Angular设置和代码的表,使用ngFor并从模板创建列表。所以代码使用AngularFire 2从Firebase传递数据。尝试新的md数据表就是问题所在。
首先,模板不会渲染。我知道我已正确设置NgModule,所以我怀疑数据源是否连接并创建此错误。这是Chrome控制台中的错误。
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.
我的members-search.component.html看起来与官方文档相同,只是我更改了模板绑定:
<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包含以下相关部分:
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');
}
我将这些数据表函数放入members-search.service.ts中的工作代码中,并使用了我在此服务上其他地方使用的connect()中的相同代码。
// md table dataSource functions.
public connect(): FirebaseListObservable<any> {
return this.af.list('Members', {
query: {
orderByChild: 'lastName'
}
});
// return this._exampleDatabase.dataChange;
}
public disconnect() {}
数据表docs和plunker在组件中创建了一个数据源和数据库,但是如果我已经拥有Firebase,那么大多数情况似乎都不是必需的。我正在学习所有这些,所以我远远没有任何专家,也许我错过了一些东西。
如果你还没有进入这个新的设置,那么这里是文档。 md表构建在cdk表的顶部,为cdk表提供样式。
答案 0 :(得分:11)
我在MD数据表中使用MD Paginator时添加了连接Firebase的代码。 Paginator使服务中的代码更加复杂。大多数代码都在它所属的服务中。享受!
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() {}
}
在ngOnInit
和类属性中进行了一些重构。
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;
});
}
注意我在行中有按钮用于删除和编辑,它们工作正常。诀窍是你需要隐藏列中的数据库密钥。
<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>
答案 1 :(得分:3)
以下解决方案有效。花了一段时间才弄清楚如何解决这个问题,我从Will Howell那里得到了Reddit Angular小组的熟练帮助。我的服务更多地涉及CRUD的东西,但那些还没有出炉。我正在为master-detail设置这个按钮来显示删除和编辑。最后一列将Firebase $key
带到DOM上,我将捕获并使用它来访问组件和服务中的CRUD函数。当我弄清楚这一点时,我会将完整的混乱,错误的代码发布到另一个具有更具体标题的Stack Overflow帖子中。
如果我像AM2数据表文档一样设置,该服务包括三个类。我不确定我喜欢这个,但现在会关注文档。
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() {}
}
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);
});
}
<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>
答案 2 :(得分:1)
我想我会为寻找此解决方案的人添加我的方法。
我试图在集合中重复使用它。它支持检索数据并按指定字段向上和向下排序。不要忘记在firebase规则中将排序字段添加到.indexOn
。
我还没有设法让分页工作,因为它太难以解决startKey!
<强>火力-datasource.ts 强>
首先定义一个模板数据源,我可以为需要这个的所有集合重用它。
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. e.g. /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 e.g. /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();
}
}
然后是一个示例用法:
<强>角色的datasource.ts 强> 在&#39;提供商&#39;中声明这一点。相关模块。 (代码未显示)
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);
}
}
现在让我们看一下UI组件:
所有-roles.component.html 强>
忽略工具栏的无关代码等。需要注意的重要部分是mat-table
和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>
所有-roles.component.ts 强>
最后在UI Layer实现。作为一种品味,它捕获MatSort
更新并将它们发送到数据源,因为我不喜欢将MatSort直接绑定到数据源层。我还在加载数据时添加了一个简单的Ajax Loader。
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();
});
}
}