首页 文章

Angular Material 2数据表连接到AngularFire2或Firebase服务?

提问于
浏览
8

我希望它只是即插即用:-)我一直在喋喋不休地打了几个小时没有我的小实验工作 . 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表提供样式 .

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

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

3 回答

  • 1

    我在MD数据表中使用MD Paginator时添加了连接到Firebase的代码 . Paginator使服务中的代码更加复杂 . 大多数代码都在它所属的服务中 . 请享用!

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

    all-members.component.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;
              });
        }
    

    all-members.component.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>
    
  • 11

    以下解决方案有效 . 花了一段时间才弄清楚如何解决这个问题,我从Will Howell那里得到了Reddit Angular小组的熟练帮助 . 我的服务更多地涉及CRUD的东西,但那些没有't baked yet. I'm设置为主要细节与按钮显示删除和编辑 . 最后一列将Firebase $key 带到了DOM上,我将这个混乱的错误代码发布到另一个具有更具体 Headers 的Stack Overflow帖子中 .

    member-admin.service.ts

    该服务包括三个类,如果设置像我已经完成的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() {}
    }
    

    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

    以为我会为寻找这个解决方案的人添加我的方法 .

    我忘了在firebase规则中将排序字段添加到 .indexOn .

    我没有设法让分页工作,因为它太难以解决startKey!

    firebase-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();
      }
    }
    

    然后是一个示例用法:

    roles-datasource.ts 在相关模块的'providers'中声明此内容 . (代码未显示)

    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组件:

    all-roles.component.html

    忽略工具栏的无关代码等 . 需要注意的重要部分是 mat-tablematSort 指令 .

    <!-- 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

    最后在UI Layer实现 . 作为一种品味,它捕获 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();
          });
        }
    
      }
    

相关问题