Угловой материал 2 таблица данных подключение к AngularFire2 или Firebase Service?
Я хотел бы, чтобы это было просто подключи и играй :-) я трепался с этим часами, и ни один из моих маленьких экспериментов не работал. Таблица данных md является новой, поэтому в интернете еще почти нет божественного знания. Найти хороший способ подключить огневую базу к столу было бы хорошим началом. Есть идеи?
В настоящее время у меня есть эта настройка. Мой код отлично работает без таблицы со стандартной угловой настройкой и кодом, используя ngFor и создавая список из шаблона. Итак, код доставляет данные с огневой базы с AngularFire 2. Опробовать новую таблицу данных 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.
Мои члены-поиск.деталь.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>
Участники-поиск.деталь.у ТС есть такие соответствующие части:
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.обслуживание.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 стиль.
3 ответа:
Я добавил код для подключения к Firebase при использовании MD Paginator для таблицы данных MD. Пагинатор делает код в сервисе более сложным. Большая часть кода находится в службе, которой он принадлежит. Наслаждайтесь!
Участник-администратор.обслуживание.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'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() {} }
Все члены.деталь.ts
Произвел некоторый рефакторинг в
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; }); }
Все члены.деталь.html
Обратите внимание, что у меня есть кнопки в строках для удаления и редактирования, и они работают хорошо. Фокус в том, что вам нужен ключ базы данных в скрытом столбце.
<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>
Работает следующее решение. Потребовалось некоторое время, чтобы понять, как решить этот вопрос, и я получил квалифицированную помощь от Уилла Хауэлла из угловой группы Reddit. Моя служба больше занимается всякой дрянью, но они еще не испечены. Я настраиваю это для master-detail с кнопками, чтобы показать delete и edit. Последняя колонка выводит Firebase
$key
на DOM, который я буду захватывать и использовать для доступа к функциям CRUD в компоненте и сервисе. Когда я все это выясню, я ... разместите полный беспорядок, err, код в другой пост переполнения стека с более конкретным названием.Участник-администратор.обслуживание.ts
Сервис включает в себя три класса, если настроить как AM2 Data Table docs, как я сделал. Я не уверен, что мне это нравится, но пока буду следовать документам.
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() {} }
Все члены.деталь.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); }); }
Все члены.деталь.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>
Подумал, что я добавлю свой подход для тех, кто ищет это решение.
Я пытался сделать это многоразовым в разных коллекциях. Он поддерживает извлечение данных и сортировку вверх и вниз по указанным полям. Не забудьте добавить поля сортировки в
.indexOn
в правилах firebase.Мне не удалось заставить пейджинг работать, потому что это слишком сложно, чтобы работать с ключом start!
Firebase-источник данных.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(); } }
Тогда пример использования:
Роли-источник данных.ts Объявите об этом в разделе "поставщики" соответствующего модуля. (код не показан)
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); } }
Теперь давайте посмотрим на компонент пользовательского интерфейса:
Все-роли.деталь.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>
Все-роли.деталь.ts
Наконец, реализация на уровне пользовательского интерфейса. По своему вкусу он захватывает обновления
MatSort
и отправляет их в источник данных, потому что мне не нравится связывать MatSort непосредственно в слой источника данных. Я также добавил простой загрузчик Ajax во время загрузки данных.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(); }); } }