Saltar al contenido

Obtenga múltiples valores de referencia de plantilla ng usando contentChildren en angular 5

Solución:

A Directive es un buen enfoque para esto, por lo que ya está pensando en la dirección correcta. Las directivas también admiten parámetros de entrada para que pueda especificar el nombre de la columna o el encabezado como parámetro de la directiva. Consulte también la documentación oficial para más detalles.

Aquí hay una directiva de muestra que utiliza este enfoque:

import { Directive, TemplateRef, Input } from '@angular/core';

@Directive({
  selector: '[tableColumn]'
})
export class TableColumnDirective {

  constructor(public readonly template: TemplateRef<any>) { }

  @Input('tableColumn') columnName: string;
}

Como puede ver, la directiva tiene una propiedad de entrada que recibirá el nombre de la columna y también inyecta el TemplateRef para que pueda acceder a él directamente desde la directiva.

Luego puede definir las columnas de esta manera:

<ng-template tableColumn="firstname" let-firstname>
   <h1>this template is for column firstName</h1>
</ng-template>
<ng-template tableColumn="lastName" let-lastname>
   <h1>this template is for column lastName</h1>
</ng-template>

En el componente, luego consulta el ContentChildren por la directiva y obtenga todas las directivas que le dan acceso a los nombres de columna y plantillas.

Aquí está el componente actualizado:

import { Component, OnInit, ContentChildren, QueryList, TemplateRef, AfterContentInit } from '@angular/core';


@Component({
  selector: 'my-table',
  template: `<h1>This is the temp component</h1>`,
  styleUrls: ['./temp.component.scss']
})
export class TempComponent implements OnInit,AfterContentInit {

  constructor() { }
  @ContentChildren(TableColumnDirective) columnList: QueryList<TableColumnDirective>;
  ngOnInit() {
  }

  ngAfterContentInit(){
    console.log('column template list');
    console.log(this.columnList.toArray());
  }

}

Aquí hay una forma ligeramente diferente de hacerlo, tal vez te guste más. Ahora lo basaré en su muestra de tabla personalizada, ya que proporcionó más información.

Puede crear una directiva que tome contenido y especifique la plantilla como contenido. Aquí hay una implementación de muestra:

@Directive({
  selector: 'custom-mat-column',
})
export class CustomMatColumnComponent {
  @Input() public columnName: string;
  @ContentChild(TemplateRef) public columnTemplate: TemplateRef<any>;
}

Entonces su plantilla de componente principal cambiará a esto:

<custom-mat-table [tableColumns]="columnList" [tableDataList]="tableDataList 
   (cellClicked)="selectTableData($event)" (onSort)="onTableSort($event)" class="css-class-admin-users-table">
  <custom-mat-column columnName="firstname">
    <ng-template let-item let-func="func">
      <div class="css-class-table-apps-name">
        <comp-avatar [image]="" [name]="item?.processedName" [size]="'small'"></comp-avatar>
        <comp-button (onClick)="func(item)" type="text">{{item?.processedName}}</comp-button>
      </div>
    </ng-template>
  </custom-mat-column>
  <custom-mat-column columnName="status">
    <ng-template #status let-item>
      <div [ngClass]="{'item-active' : item?.status, 'item-inactive' : !item?.status}"
        class="css-class-table-apps-name">{{item?.status | TextCaseConverter}}
      </div>
    </ng-template>
  </custom-mat-column>
  <custom-mat-column columnName="lastname">
    <ng-template #lastname let-item>
      <div class="css-class-table-apps-name">
        {{item?.lastname}}</div>
    </ng-template>
  </custom-mat-column>
</custom-mat-table>

Es necesario cambiar el componente de su tabla personalizada. en lugar de recibir el templateNameList necesita generarlo desde el ContentChildren Bajo demanda.

@Component({
    selector: 'custom-mat-table',
    templateUrl: './customTable.component.html',
    styleUrls: ['./customTable.component.scss']
})
export class NgMatTableComponent<T> implements OnChanges, AfterViewInit {
  @ContentChildren(CustomMatColumnComponent) columnDefinitions: QueryList<CustomMatColumnComponent>;
  templateNameList: { [key: string]: TemplateRef<any> } {
    if (this.columnDefinitions != null) {
      const columnTemplates: { [key: string]: TemplateRef<any> } = {};
      for (const columnDefinition of this.columnDefinitions.toArray()) {
        columnTemplates[columnDefinition.columnName] = columnDefinition.columnTemplate;
      }
      return columnTemplates;
    } else {
      return {};
    }
  };
  @Input() tableColumns: TableColumns[] = [];
  @Input() tableDataList: T[] = [];
  @Output() cellClicked: EventEmitter<PayloadType> = new EventEmitter();
  @Output() onSort: EventEmitter<TableSortEventData> = new EventEmitter();
  displayedColumns: string[] = [];
  tableDataSource: TableDataSource<T>;
  @ViewChild(MatSort) sort: MatSort;

  constructor() {
      this.tableDataSource = new TableDataSource<T>();
  }

  onCellClick(e: T, options?: any) {
      this.cellClicked.emit({ 'row': e, 'options': options });
  }

  ngOnChanges(change: SimpleChanges) {
      if (change['tableDataList']) {
          this.tableDataSource.emitTableData(this.tableDataList);
          this.displayedColumns = this.tableColumns.map(x => x.displayCol);
      }

  }

  ngAfterViewInit() {
      this.tableDataSource.sort = this.sort;
  }

  sortTable(e: any) {
      const { active: sortColumn, direction: sortOrder } = e;
      this.onSort.emit({ sortColumn, sortOrder });
  }
}

Si no le gusta este segundo enfoque, aún puede usar lo que sugerí en la muestra original de la misma manera. La única diferencia es cómo se ve en la plantilla. También creé una muestra de StackBlitz para que puedas verla en la práctica.

Tuve que construir muchos componentes de tabla que usaban Angular Material’s MatTable, y en algún momento decidí ahorrarme algo de tiempo a largo plazo construyendo una mesa base que sea dinámica y reutilizable. Agregué un poco más de contexto / proceso de pensamiento sobre cómo poner en marcha una tabla reutilizable dinámica mínima, antes de hablar sobre cómo agregarle una función específica.

Consejos para construir una mesa dinámica y reutilizable

Lo primero que hice (después de agregar Angular Material al proyecto) fue determinar cómo quiero que los consumidores usen mi tabla. Decidí que cualquier comportamiento a nivel de tabla (habilitar / deshabilitar la paginación) sería controlado por @Inputestá en el componente de tabla. Sin embargo, a medida que lo desarrollé más, me di cuenta de que la mayoría de las nuevas funciones que necesitaba realmente deberían controlarse por columna. El resto de esta respuesta se centra en la configuración por columna.

TableColumnConfig Interfaz: agregando una nueva característica

Comencé definiendo una interfaz para un objeto de configuración (como lo hizo OP con TableColumns excepto que el mío se llama TableColumnConfig. El mínimo necesario para la funcionalidad dinámica y reutilizable son dos cadenas que usa para acceder a los datos en cada fila y para mostrar el nombre de la columna (yo uso key y displayName).

Si queremos agregar la capacidad para que los consumidores del componente pasen una plantilla de celda personalizada, primero agregaría una propiedad al TableColumnConfig interfaz así:

import { TemplateRef } from '@angular/core';

export interface TableColumnConfig {
  displayName: string;
  key: string;
  customCellTemplate?: TemplateRef<any>; // custom cell template!
}

my-table-component.ts

Creo que comencé con el esquema de material angular para generar un componente de tabla, pero no me gustó la cantidad de texto estándar para algo mínimo como este ejemplo (es bastante fácil agregar paginación y clasificación más adelante).

No necesita hacer nada especial en table-component.ts para personalizar la funcionalidad de la plantilla de celda personalizada (solo tenga en cuenta que estamos esperando TableColumnConfig[] del componente consumidor), pero mostrando el código siguiente para que esté completo. La mayoría de las veces, cuando necesitaba agregar una función por columna, nunca tuve que meterme con este archivo.

import { Component, OnInit, Input } from '@angular/core';
import { MatTableDataSource } from '@angular/material';
import { TableColumnConfig } from './table-column-config';

@Component({
  selector: 'app-my-table',
  templateUrl: './my-table.component.html',
  styleUrls: ['./my-table.component.css']
})
export class MyTableComponent implements OnInit {
  @Input() data: any[];
  @Input() columnConfigs: TableColumnConfig[];
  dataSource: MatTableDataSource<any>;
  // need a string array for *matHeaderRowDef and *matRowDef
  displayedColumns: string[];

  ngOnInit() {
    this.displayedColumns = this.columnConfigs.map(config => config.key);
    this.dataSource = new MatTableDataSource(this.data);
  }
}

my-table-component.html

Enfoque similar al que mostró el OP en su respuesta. Desde que agregué customCellTemplate como una propiedad para TableColumnConfig, acceder a él se ve un poco más limpio. También solo una nota de que para esta demostración decidí exponer solo los datos de la columna a customCellTemplates, pero podría devolver fácilmente la fila completa si es necesario cambiando $implicit: row[col.key] para $implicit: row

<div class="mat-elevation-z8">
  <mat-table class="full-width-table" [dataSource]="dataSource">
    <!-- NgFor Columns -->
    <ng-container *ngFor="let col of columnConfigs" matColumnDef="{{ col.key }}">
      <mat-header-cell *matHeaderCellDef> {{ col.displayName }}
      </mat-header-cell>

      <mat-cell *matCellDef="let row">
        <!-- handle custom cell templates -->
        <div *ngIf="!col.customCellTemplate; else customCellTemplate">
            {{ row[col.key] }}
        </div>
        <ng-template #customCellTemplate>
          <!-- for now, only exposing row[col.key] instead of entire row -->
          <ng-template [ngTemplateOutlet]="col.customCellTemplate"
            [ngTemplateOutletContext]="{ $implicit: row[col.key] }">
          </ng-template>
        </ng-template>
      </mat-cell>
    </ng-container>

    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
  </mat-table>
</div>

Ejemplo: consumir componente

Ejemplo de caso de uso en el que queremos texto con estilo en una columna

app-component.html

Para este ejemplo mínimo, la tabla solo tiene dos entradas. Me gusta definir el <ng-template>s para customCellTemplates en la parte inferior del archivo en lugar de dentro de la etiqueta de la tabla en sí para una mejor legibilidad en mi opinión.

<app-my-table [data]="tableData" [columnConfigs]="columnConfigs">
</app-my-table>

<!-- Custom cell template for color column -->
<!-- name the $implicit variable 'let-whateverIwant' -->
<ng-template #customCell let-colorData>
  <span [ngStyle]="{'color': colorData}">{{colorData}}</span>
</ng-template>

app-component.ts

export class AppComponent implements OnInit {
  @ViewChild("customCell", { static: true })
  customCell: TemplateRef<any>;
  columnConfigs: TableColumnConfig[];

  tableData = [
    { id: 1, name: "Chris", color: "#FF9900" },
    { id: 2, name: "Akash", color: "blue" }
  ];

  // we can't reference our {static:true} TemplateRef until ngOnInit
  ngOnInit() {
    this.columnConfigs = [
      { key: "id", displayName: "ID" },
      { key: "name", displayName: "Name" },
      {
        key: "color",
        displayName: "Favorite Color",
        customCellTemplate: this.customCell
      }
    ];
  }
}

Echa un vistazo a mi demostración de StackBlitz para ver algunos comentarios de código más.

He construido un componente de tabla en mi biblioteca Easy Angular https://github.com/adriandavidbrand/ngx-ez/tree/master/projects/ngx-ez/src/lib/ez-table

Cada columna puede tomar una plantilla a través de ViewChild

@ContentChild(TemplateRef)
template: TemplateRef<any>;

La tabla usa ContentChildren para obtener las columnas.

@ContentChildren(EzColumnComponent)
columns: QueryList<EzColumnComponent>;

y el componente de tabla pasa el elemento actual con un contexto al renderizar

<ng-container *ngTemplateOutlet="column.template || defaultColumTemplate;context:{ $implicit: item, index: i }"></ng-container>

y se usa como

<ez-table [data]="data">
  <ez-column heading="Heading" property="prop">
    <ng-template let-item>
      Use item view variable in template here
    </ng-template>
  </ez-column>
<ez-table>

Aquí hay una demostración de cómo funciona.

Hay bastante en esta tabla, pero toda la fuente está en GitHub.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *