代码之家  ›  专栏  ›  技术社区  ›  Akash

使用5中的contentChildren获取多个ng模板ref值

  •  0
  • Akash  · 技术社区  · 5 年前

    我想通过多次考试 ng-template component (我的表组件),内容投影。现在我需要得到每个传递的引用值 ng模板 所以我可以用这个值来知道,哪个模板是为哪个列传递的。基本上,我正在创建一个可重用的表组件(在Angular material表的顶部),用户可以为每一列传递一个单独的模板。

    温度组件ts

    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(TemplateRef) tempList: QueryList<TemplateRef<any>>;
    
      ngOnInit() {
      }
    
      ngAfterContentInit() {
          console.log('template list');
          console.log(this.tempList);
      }
    }
    

    应用程序组件.html

    <my-table>
        <ng-template #column1 let-company let-func="func">
            <h1>this template is for column 1</h1>
        </ng-template>
        <ng-template #column2 let-company let-func="func">
            <h1>this template is for column 2</h1>
        </ng-template>
    </my-table>
    

    我可以为每一列创建指令,但是列中的任何一个都可能更改,所以指令路由将不起作用。我认为,组件用户将传递每个列模板,其中template ref值作为列标题值,例如,如果用户传递 ng模板 对于“firstName”列,应该是这样的,

     <ng-template #firstName let-firstname>
         <h1>this template is for column firstName</h1>
     </ng-template> 
    

    ng模板 他们的参考,这样我就可以知道,哪个模板属于哪个列。

    0 回复  |  直到 4 年前
        1
  •  14
  •   luiscla27    4 年前

    Directive 这是一个很好的方法,所以你已经在正确的方向思考。指令还支持输入参数,因此可以将列名或标题指定为指令的参数。同时检查 official documentation 更多细节。

    下面是使用此方法的示例指令:

    import { Directive, TemplateRef, Input } from '@angular/core';
    
    @Directive({
      selector: '[tableColumn]'
    })
    export class TableColumnDirective {
    
      constructor(public readonly template: TemplateRef<any>) { }
    
      @Input('tableColumn') columnName: string;
    }
    

    如您所见,该指令有一个将接收列名的输入属性,并且它还注入 TemplateRef 因此您可以直接从指令访问它。

    然后可以这样定义列:

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

    然后在组件中查询 ContentChildren 并获取所有允许您访问列名和模板的指令。

    以下是更新的组件:

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

    这里有一个稍微不同的方法,也许你更喜欢这个。由于您提供了更多信息,我现在将以您的自定义表格示例为基础。

    您可以创建一个接受内容的指令,并将模板指定为内容。下面是一个示例实现:

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

    然后父组件模板将更改为:

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

    您的自定义表组件需要更改。而不是收到 templateNameList 满足儿童 随需应变。

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

    如果您不喜欢第二种方法,您仍然可以使用我在原始示例中建议的方法。唯一的区别是它在模板中的外观。 我还创造了一个 StackBlitz sample 所以你可以在实践中看到。

        2
  •  3
  •   Anto Antony    5 年前

    还有另一种创建自定义表组件的方法。您可以访问整个行,而不是只公开列。所以你可以直接控制整个列。

    自定义-表.component.html

    <table>
    
        <!-- Caption -->
        <ng-container *ngTemplateOutlet="captionTemplate ? captionTemplate: defaultCaption; context:{$implicit: caption}">
        </ng-container>
    
        <!-- Header -->
        <thead>
            <ng-container *ngTemplateOutlet="headerTemplate ? headerTemplate: defaultHeader; context:{$implicit: columns}">
            </ng-container>
        </thead>
    
        <!-- Body -->
        <tbody>
            <!-- Here we will provide custom row Template -->
            <ng-template ngFor let-rowData let-rowIndex="index" [ngForOf]="values">
                <ng-container
                    *ngTemplateOutlet="bodyTemplate ? bodyTemplate: defaultBody; context:{$implicit: rowData,columns: columns , index:rowIndex }">
                </ng-container>
            </ng-template>
        </tbody>
    
        <!-- Footer -->
        <tfoot>
            <ng-template ngFor let-rowData let-rowIndex="index" [ngForOf]="footerValues">
                <ng-container
                    *ngTemplateOutlet="footerTemplate ? footerTemplate: defaultFooter; context:{$implicit: rowData,columns: columns , index:rowIndex }">
                </ng-container>
            </ng-template>
        </tfoot>
    
    </table>
    
    <!-- Caption Default Template -->
    <ng-template #defaultCaptio let-caption>
        <caption *ngIf="caption">{{caption}}</caption>
    </ng-template>
    
    <!-- Header Default Template -->
    <ng-template #defaultHeader let-columns>
        <tr>
            <th *ngFor="let column of columns">{{column.title}}</th>
        </tr>
    </ng-template>
    
    <!-- Body Default Template -->
    <ng-template #defaultBody let-item let-columns="columns">
        <tr>
            <td *ngFor="let column of columns">{{item[column.key]}}</td>
        </tr>
    </ng-template>
    
    <!-- Footer Default Template -->
    <ng-template #defaultFooter>
        <tr *ngFor="let item of footerValues">
            <td *ngFor="let column of columns">{{item[column.key]}}</td>
        </tr>
    </ng-template>
    

    自定义-表.component.ts

    import {
      Component,
      OnInit,
      Input,
      TemplateRef,
      ContentChild
    } from "@angular/core";
    
    @Component({
      selector: "app-custom-table",
      templateUrl: "./custom-table.component.html",
      styleUrls: ["./custom-table.component.css"]
    })
    export class CustomTableComponent implements OnInit {
      @Input()
      caption: string;
    
      @Input()
      columns: { title: string; key: string }[] = [];
    
      @Input()
      values: any[] = [];
    
      @Input()
      footerValues: any[] = [];
    
      @ContentChild("caption", { static: false })
      captionTemplate: TemplateRef<any>;
    
      @ContentChild("header", { static: false })
      headerTemplate: TemplateRef<any>;
    
      @ContentChild("body", { static: false })
      bodyTemplate: TemplateRef<any>;
    
      @ContentChild("footer", { static: false })
      footerTemplate: TemplateRef<any>;
    
      constructor() {}
    
      ngOnInit() {}
    }
    

    现在您可以提供以下详细信息,

    <app-custom-table [columns]="columns" [values]="values" [footerValues]="footerValues">
    
        <!-- Caption Custom Template -->
        <ng-template #caption>
            <caption>Custom Table</caption>
        </ng-template>
    
        <!-- Header Custom Template -->
        <ng-template #header let-columns>
            <tr>
                <th *ngFor="let column of columns">[{{column.title}}]</th>
            </tr>
        </ng-template>
    
        <!-- Body Custom Template -->
        <ng-template #body let-item let-columns="columns">
            <tr *ngIf="item.id === 1 else diff">
                <td *ngFor="let column of columns">
                    <span *ngIf="column.title === 'Name'" style="background-color: green">{{item[column.key]}}</span>
                    <span *ngIf="column.title !== 'Name'">{{item[column.key]}}</span>
                </td>
            </tr>
            <ng-template #diff>
                <tr style="background-color: red">
                    <td *ngFor="let column of columns">{{item[column.key]}}</td>
                </tr>
            </ng-template>
        </ng-template>
    
        <!-- Footer Custom Template -->
        <ng-template #footer let-item let-columns="columns">
            <tr>
                <td [colSpan]="columns.length">{{item.copyrightDetails}}</td>
            </tr>
        </ng-template>
    </app-custom-table>
    

    我已经为同样的目标创建了一个stackblitz。请参考 this .

        3
  •  3
  •   Chris Newman    5 年前

    我已经建立了许多表的组成部分,使用角材料的 MatTable ,在某个时刻,我决定通过构建一个动态的、可重用的基表来节省自己一些时间。在讨论如何向表中添加特定特性之前,我已经添加了更多的上下文/思考过程,讨论了如何使用一个最小的动态可重用表来启动和运行。

    我做的第一件事(在向项目中添加角度材质之后)是确定我希望消费者如何使用我的表。我决定任何表级行为(启用/禁用分页)都由 @Input

    TableColumnConfig

    我首先为一个配置对象定义了一个接口(就像OP对 TableColumns TableColumnConfig表 key displayName

    如果我们想为组件的使用者添加传入自定义单元格模板的功能,我首先要向 TableColumnConfig表 这样的接口:

    import { TemplateRef } from '@angular/core';
    
    export interface TableColumnConfig {
      displayName: string;
      key: string;
      customCellTemplate?: TemplateRef<any>; // custom cell template!
    }
    

    我的桌子-组件.ts

    我相信我是从生成表格组件的角度材质示意图开始的,但是我不喜欢像这个示例这样简单的最小值的样板文件的数量(以后添加分页和排序很容易)。

    TableColumnConfig[] 从消费组件),但显示以下代码的完整性。大多数时候,当我需要添加一个每列的特性时,我甚至从不需要处理这个文件。

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

    我的桌子-组件.html

    customCellTemplate 作为财产 TableColumnConfig表 $implicit: row[col.key] $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>
    

    示例:消费组件

    我们希望在列中使用样式化文本的示例用例

    应用程序-组件.html

    对于这个简单的例子,表只有两个输入。我喜欢定义 <ng-template> 用于customCellTemplates的,位于文件的底部,而不是表标记本身的内部,以提高可读性。

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

    应用程序-组件.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
          }
        ];
      }
    }
    

    StackBlitz demo 更多的代码注释。

        4
  •  2
  •   Adrian Brand    5 年前

    我在我的库中构建了一个表组件 https://github.com/adriandavidbrand/ngx-ez/tree/master/projects/ngx-ez/src/lib/ez-table

    每列都可以通过ViewChild获取模板

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

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

    并且表组件在呈现时将当前项与上下文一起传递

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

    它的用法是

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

    下面是一个演示它是如何工作的

    https://stackblitz.com/edit/angular-npn1p1

    这个表有很多内容,但是所有的源代码都在GitHub上。

        5
  •  1
  •   Akash    5 年前

    以下是我的业务要求,

    1. 表应可排序

    所以我需要完全控制每个单元格模板和单元格中任何元素引发的事件。

    <div class="mat-elevation-z8 css-class-table">
      <mat-table #table [dataSource]="tableDataSource" matSort (matSortChange)="sortTable($event)">
        <ng-container *ngFor="let col of tableColumns; let colIndex=index" matColumnDef="{{col?.displayCol}}">
          <mat-header-cell *matHeaderCellDef mat-sort-header class="css-class-table-header css-class-table-header-visibility">
            {{col?.headerCol}}
          </mat-header-cell>
          <mat-cell *matCellDef="let row; let i=index" >
            <ng-container [ngTemplateOutlet]="templateNameList[col?.displayCol] || noTemplate"
              [ngTemplateOutletContext]="{$implicit:row,func:onCellClick.bind(this)}">
            </ng-container>
            <ng-template #noTemplate>
              {{row[col.displayCol]}}
            </ng-template>
    
          </mat-cell>
        </ng-container>
    
        <mat-header-row *matHeaderRowDef="displayedColumns; let i=index"></mat-header-row>
        <mat-row *matRowDef="let row; columns: displayedColumns; let i=index" class="css-class-grid-row"></mat-row>
    
      </mat-table>
    
    </div>
    

    import { Component, Input, ViewChild, AfterViewInit, OnChanges, Output, EventEmitter, TemplateRef, SimpleChanges, ContentChild, ContentChildren } from '@angular/core';
    import { MatTableDataSource, MatSort, MatPaginator } from '@angular/material';
    import { DataSource } from '@angular/cdk/table';
    import { BehaviorSubject } from 'rxjs/BehaviorSubject';
    import { Observable } from 'rxjs/Observable';
    
    export interface TableColumns {
        displayCol: string;
        headerCol: string;
    }
    
    export interface TableSortEventData {
        sortColumn: string;
        sortOrder: string;
    }
    
    export interface PayloadType {
        row: any;
        options?: any;
    }
    
    
    @Component({
        selector: 'custom-mat-table',
        templateUrl: './customTable.component.html',
        styleUrls: ['./customTable.component.scss']
    })
    export class NgMatTableComponent<T> implements OnChanges, AfterViewInit {
        @Input() templateNameList: Object;
        @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 });
        }
    
    }
    
    export class TableDataSource<T> extends DataSource<T> {
    
        tableDataSubject = new BehaviorSubject<T[]>([]);
        sort: MatSort | null;
        private _sort;
    
        constructor() {
            super();
        }
    
        emitTableData(data: T[]) {
            this.tableDataSubject.next(data);
        }
    
        connect(): Observable<T[]> {
            return this.tableDataSubject.asObservable();
        }
    
        disconnect() {
            this.tableDataSubject.complete();
        }
    }
    

    <custom-mat-table [tableColumns]="columnList" [tableDataList]="tableDataList"
    [templateNameList]="{'firstname':firstname,'lastname':lastname,'status':status}"
    (cellClicked)="selectTableData($event)" (onSort)="onTableSort($event)" class="css-class-admin-users-table">
    <ng-template #firstname 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>
    <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>
    <ng-template #lastname let-item>
        <div class="css-class-table-apps-name">
            {{item?.lastname}}</div>
    </ng-template>
    </custom-mat-table>
    

    父组件.ts

    columnList: TableColumns[] = [
        { displayCol: 'firstname', headerCol: 'First Name' },
        { displayCol: 'lastname', headerCol: 'Last Name' },
        { displayCol: 'status', headerCol: 'Status' }
    ];
    
    templateList: Object = "{'firstname':firstname,'lastname':lastname,'status':status}";
    
    onTableSort(e: TableSortEventData) {
        this.sortQueryParam = {};
        if (e && e.sortOrder !== '') {
            this.sortQueryParam['sortBy'] = e.sortColumn;
            this.sortQueryParam['order'] = e.sortOrder.toUpperCase();
        }
        else {
            this.sortQueryParam = null;
        }
    }