Угловой материал 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 стиль.

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

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

3 7

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

  }