Угловая таблица материалов: общая сортировка таблиц - PullRequest
0 голосов
/ 25 августа 2018

Я пытаюсь создать универсальный компонент данных с возможностью повторного использования с разбивкой на страницы / сортировку / фильтрацию, который использует таблицу материалов, но удаляет значительную часть шаблона, используемого при его использовании, во всем приложении.

Целями этой таблицы являются следующие:

  • использование таблицы mat с paginator / mat-sort / etc. без связки в каждом шаблоне и компоненте, который его использует
    • берет наблюдаемый источник данных и обрабатывает подписку и управление данными
    • допускает проекцию содержимого с учетом данных (например, * matCellDef)
    • возможно в будущем перейти от таблицы mat и использовать другого поставщика данных, не затрагивая ничего за пределами универсального компонента с данными (так что никаких директив или компонентов mat * нигде, кроме самого универсального компонента с данными)

Я уже разработал это, и по большей части он работает отлично, но у меня возникла странная проблема, связанная с сортировкой. Все загружается правильно, и сортировка работает отлично - по первому столбцу. Я могу сортировать столько раз, сколько захочу, и это будет хорошо работать в первом столбце. Как только я выполняю сортировку по любому другому столбцу, я получаю сообщение «Ошибка ОШИБКИ: предоставлено повторяющееся имя определения столбца: ...», и после этого действия сортировки для любого столбца (включая первый) просто генерируют эту ошибку и ничего не делают. Другие взаимодействия таблиц (нумерация страниц, фильтрация) также перестают работать в этом случае.

Не имеет значения, в каком столбце, в каком порядке или в каких атрибутах определен шаблон или нет и т. Д.

Ниже приведен пример использования компонента и всего соответствующего кода. Заранее спасибо за помощь!

Пример файла с использованием моего компонента данных

<app-datatable [dataSource]="testData">
 <app-datatable-column name="name">
  <ng-container *datatableCell="let data">
    <a href="somewhere/{{data.id}}">{{data.name}}</a>
  </ng-container>
 </app-datatable-column>
 <app-datatable-column name="age" label="Years on Earth"></app-datatable-column>

datatable.component.ts

@Component({
  selector: 'app-datatable',
  template: `
    <mat-form-field *ngIf="filterable">
      <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
    </mat-form-field>  
    <table mat-table [dataSource]="data">
      <tr mat-header-row *matHeaderRowDef="columns"></tr>
      <tr mat-row *matRowDef="let row; columns: columns"></tr>
    </table>
    <mat-progress-bar mode="indeterminate" *ngIf="loading"></mat-progress-bar>
    <mat-paginator [pageSizeOptions]="pageSizeOptions" *ngIf="pagination"></mat-paginator>`,
})
export class DatatableComponent<T> implements AfterContentInit {
  /** The list of columns which should appear - this will default to all defined columns if not set */
  @Input() columns: string[];
  /** The datasource from which the table should populate */
  @Input() dataSource: Observable<DatatableDataSource<T>>;
  /** Toggles for pagination, sorting, and filtering */
  @Input() pagination: boolean = true;
  @Input() sortable: boolean = true;
  @Input() filterable: boolean = true;
  /** Page size options provided */
  @Input() pageSizeOptions: number[] = [10, 25, 50, 100];
  /** Default sort options */
  @Input() defaultSortColumn: string;
  @Input() defaultSortDirection: 'asc' | 'desc' = 'asc';

  /** Column definitions added via the DatatableColumn component */
  @ContentChildren(DatatableColumnComponent) datatableColumns: QueryList<DatatableColumnComponent<T>>;

  /** References to the table and row definitions in this template */
  @ViewChild(MatTable) table: MatTable<T>;
  @ViewChild(MatHeaderRowDef) headerRows: MatHeaderRowDef;
  @ViewChildren(MatRowDef) rows: QueryList<MatRowDef<T>>;

  /** Paginator */
  @ViewChild(MatPaginator) paginator: MatPaginator;
  /** Sorter */
  public sort: MatSort = new MatSort();
  /** The data resultant from the provided dataSource - this is fed directly into the table for display */
  public data: MatTableDataSource<T>;
  /** Whether the data is loading from the source or not */
  public loading: boolean = true;

  /**
   * Apply filter to current data source
   * @param filterValue The value to filter against
   * @return void
   */
  public applyFilter(filterValue: string): void {
    if (this.filterable) {
      this.data.filter = filterValue.trim().toLowerCase();

      if (this.data.paginator) {
        this.data.paginator.firstPage();
      }
    }
  }

  ngAfterContentInit() {
    // define columns if not already set
    this.columns = this.columns || this.datatableColumns.map(column => column.name);

    // set data result and status from source
    this.dataSource.pipe(takeUntil(this.ngUnsubscribe)).subscribe((data: DatatableDataSource<T>) => {
      this.loading = data.loading;
      this.data = new MatTableDataSource<T>(data.data);
      this.data.sort = this.sort;
      this.data.paginator = (this.pagination) ? this.paginator : null;

      // if there is sorting, default sort must also be set for table interactions to work
       if (this.sortable) {
        // get a sub-set of columns which are defined as sortable
        const sortableColumns = this.datatableColumns
          .filter(column => (column.sortable && this.columns.includes(column.name)))
          .map(column => column.name);

        // only implement sorting if there are sortable columns
        if (sortableColumns.length > 0) {
          this.data.sort = this.sort;
          this.sort.sort(<MatSortable>{
            id: (this.defaultSortColumn && sortableColumns.includes(this.defaultSortColumn)) ? this.defaultSortColumn : sortableColumns[0],
            start: this.defaultSortDirection
          });
        }
      }
    });

    // register datatable-columns to the table
    this.datatableColumns.forEach(datatableColumn => {
      this.table.addColumnDef(datatableColumn.columnDef);
      // register sort header for each sortable column
      if (datatableColumn.sortable) {
        this.sort.register(<MatSortable>{
          id: datatableColumn.name,
          start: 'asc'
        });

        datatableColumn.sortUpdate.pipe(takeUntil(this.ngUnsubscribe)).subscribe((column: string) => {
          this.sort.sort(<MatSortable>{
            id: column,
            start: 'asc'
          });
        });
      }
    });

    // send updated sort direction information back to column for display update when sorting event occurs
    this.sort.sortChange.subscribe(event => {
      this.datatableColumns.find(column => column.name = event.active).sortDirection = event.direction;
    });
  }
}

DataTable-column.component.ts

@Component({
  selector: 'app-datatable-column',
  template: `
    <ng-container matColumnDef>
      <th mat-header-cell *matHeaderCellDef
        (click)="sort()"
        [class.datatable-header-sort]="sortable"
        [class.datatable-header-sort-asc]="sortable && sortDirection === 'asc'"
        [class.datatable-header-sort-desc]="sortable && sortDirection === 'desc'">
        {{getTitle()}}
        <mat-icon *ngIf="sortable">arrow_right_alt</mat-icon>
      </th>
      <td mat-cell *matCellDef="let data">
        <ng-container *ngIf="!template">{{getData(data)}}</ng-container>
        <ng-container *ngTemplateOutlet="template; context: {$implicit: data}"></ng-container>
      </td>
    </ng-container>
  `,
  styleUrls: ['./datatable-column.component.scss']
})
export class DatatableColumnComponent<T> implements OnDestroy, OnInit {
  /** The current direction of the sorting of the column */
  public sortDirection: SortDirection = '';

  /** Column name that should be used to reference this column. */
  @Input()
  get name(): string { return this._name; }
  set name(name: string) {
    this._name = name;
    this.columnDef.name = name;
  }
  _name: string;

  /**
   * Text label that should be used for the column header. If this property is not
   * set, the header text will default to the column name.
   */
  @Input() label: string;

  /**
   * Accessor function to retrieve the data should be provided to the cell. If this
   * property is not set, the data cells will assume that the column name is the same
   * as the data property the cells should display.
   */
  @Input() dataAccessor: ((data: T, name: string) => string);

  /** Alignment of the cell values. */
  @Input() align: 'before' | 'after' = 'before';

  /** Whether the column is sortable */
  @Input() sortable: boolean = true;

  /** Event to emit when sorting is updated */
  @Output() sortUpdate = new EventEmitter<string>();

  /** Reference to column definitions and sort headers */
  @ViewChild(MatColumnDef) columnDef: MatColumnDef;
  @ViewChild(MatSortHeader) sortHeader: MatSortHeader;

  @ContentChild(DatatableCellDirective, {read: TemplateRef}) template;

  constructor(@Optional() public table: MatTable<any>) { }

  /**
   * Gives a formatted version of the name if label is not present
   * @return The formatted label
   */
  public getTitle(): string {
    return this.label || startCase(this.name);
  }

  public getData(data: T): any {
    return this.dataAccessor ? this.dataAccessor(data, this.name) : (data as any)[this.name];
  }

  /**
   * Triggers a sort action on this column by emitting a sort update action to the parent datatable
   */
  public sort(): void {
    this.sortUpdate.emit(this.name);
  }

  ngOnInit() {
    if (this.table) {
      this.table.addColumnDef(this.columnDef);
    }
  }

  ngOnDestroy() {
    if (this.table) {
      this.table.removeColumnDef(this.columnDef);
    }
  }
}

DataTable-cell.directive.ts

@Directive({
    selector: '[datatableCell]'
})
export class DatatableCellDirective { 
    constructor(public template: TemplateRef<any>){}
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...