使用 angular 5 中的 contentChildren 获取多个 ng-template ref 值 [英] Get multiple ng-template ref values using contentChildren in angular 5

查看:23
本文介绍了使用 angular 5 中的 contentChildren 获取多个 ng-template ref 值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将多个 ng-template 传递给我的可重用 component(my-table 组件),内容投影.现在我需要获取每个传递的 ng-template 的参考值,以便我可以使用该值来知道为哪一列传递了哪个模板.基本上,我正在创建一个可重用的表组件(在 Angular 材料表之上),用户可以在其中为每列传递一个单独的模板.

请提出建议 - 或者有更好的方法吗?

temp.component.ts

import { Component, OnInit, ContentChildren, QueryList, TemplateRef, AfterContentInit } from '@angular/core';@成分({选择器:'我的桌子',模板:`<h1>这是临时组件</h1>`,styleUrls: ['./temp.component.scss']})导出类 TempComponent 实现 OnInit, AfterContentInit {构造函数(){}@ContentChildren(TemplateRef) tempList: QueryList>;ngOnInit() {}ngAfterContentInit() {console.log('模板列表');console.log(this.tempList);}}

app.component.html

<ng-template #column1 let-company let-func=func"><h1>该模板用于第1列</h1></ng-模板><ng-template #column2 let-company let-func=func"><h1>该模板用于第2列</h1></ng-模板></my-table>

我可以为每一列创建指令,但没有任何列可能会改变,因此指令路由将不起作用.我在想,组件用户将使用模板引用值作为列标题值传递每个列模板,例如,如果用户为firstName"传递一个 ng-template列,它应该是这样的,

 <h1>此模板用于列 firstName</h1></ng-模板>

而且我需要一种方法来获取所有提供的 ng-template 及其引用,以便我知道哪个模板属于哪个列.

解决方案

Directive 是解决此问题的好方法,因此您已经在朝着正确的方向思考.指令还支持输入参数,因此您可以指定列名称或标题作为指令的参数.另请查看 官方文档 了解更多详情.

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

import { Directive, TemplateRef, Input } from '@angular/core';@指示({选择器:'[tableColumn]'})导出类 TableColumnDirective {构造函数(公共只读模板:TemplateRef){}@Input('tableColumn') 列名:字符串;}

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

然后您可以像这样定义列:

<h1>此模板用于列 firstName</h1></ng-模板><ng-template tableColumn="lastName";让姓氏><h1>此模板用于列 lastName</h1></ng-模板>

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

这是更新的组件:

import { Component, OnInit, ContentChildren, QueryList, TemplateRef, AfterContentInit } from '@angular/core';@成分({选择器:'我的桌子',模板:`<h1>这是临时组件</h1>`,styleUrls: ['./temp.component.scss']})导出类 TempComponent 实现 OnInit,AfterContentInit {构造函数(){}@ContentChildren(TableColumnDirective) columnList: QueryList;ngOnInit() {}ngAfterContentInit(){console.log('列模板列表');console.log(this.columnList.toArray());}}

这里有一种稍微不同的方法,也许你更喜欢这个.由于您提供了更多信息,我现在将根据您的自定义表格示例进行分析.

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

@Directive({选择器:'自定义垫列',})导出类 CustomMatColumnComponent {@Input() 公共列名:字符串;@ContentChild(TemplateRef) public columnTemplate: TemplateRef;}

然后你的父组件模板会变成这样:

</ng-模板></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}}

</ng-模板></custom-mat-column><custom-mat-column columnName="lastname"><ng-template #lastname let-item><div class="css-class-table-apps-name">{{item?.lastname}}</div></ng-模板></custom-mat-column></custom-mat-table>

您的自定义表格组件需要更改.它需要根据需要从 ContentChildren 生成它,而不是接收 templateNameList.

@Component({选择器:'自定义垫表',templateUrl: './customTable.component.html',styleUrls: ['./customTable.component.scss']})导出类 NgMatTableComponent实现 OnChanges,AfterViewInit {@ContentChildren(CustomMatColumnComponent) columnDefinitions: QueryList;templateNameList: { [key: string]: TemplateRef} {如果(this.columnDefinitions != null){const columnTemplates: { [key: string]: TemplateRef<any>} = {};for (const columnDefinition of this.columnDefinitions.toArray()) {columnTemplates[columnDefinition.columnName] = columnDefinition.columnTemplate;}返回列模板;} 别的 {返回 {};}};@Input() tableColumns: TableColumns[] = [];@Input() tableDataList: T[] = [];@Output() cellClicked: EventEmitter= new EventEmitter();@Output() onSort: EventEmitter= new EventEmitter();显示列:字符串[] = [];tableDataSource: TableDataSource;@ViewChild(MatSort) 排序:MatSort;构造函数(){this.tableDataSource = new TableDataSource();}onCellClick(e: T, options?: any) {this.cellClicked.emit({ 'row': e, 'options': options });}ngOnChanges(更改:SimpleChanges){如果(更改['tableDataList']){this.tableDataSource.emitTableData(this.tableDataList);this.displayedColumns = this.tableColumns.map(x => x.displayCol);}}ngAfterViewInit() {this.tableDataSource.sort = this.sort;}sortTable(e: 任意) {const { 活动:sortColumn,方向:sortOrder } = e;this.onSort.emit({ sortColumn, sortOrder });}}

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

I am trying to pass multiple ng-template to my reusable component (my-table component), content projection. Now I need to get the reference value of each passed ng-template so I can use that value to know, which template is passed for which column. Basically I am creating a reusable table component (on top of Angular material table) where user can pass an separate template for each column.

Kindly suggest - OR is there a better approach of doing this?

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

app.component.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>

I can create directive for each column, but than no of column might change so directive route will not work. I am thinking that, component user will pass each column template with template ref value as column header value, for example, if user is passing an ng-template for "firstName" column, it should be like ,

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

And I need a way to get all the provided ng-template with their ref so I can know, which template belongs to which column.

解决方案

A Directive is a good approach for this so you are already thinking in the right direction. Directives support also input parameters so you can specify the column name or header as the parameter to the directive. Check also the official documentation for more details.

Here is a sample directive using this approach:

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

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

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

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

As you can see the directive has an input property that will receive the column name and also it injects the TemplateRef so you can access it directly from the directive.

You can then define the columns like this:

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

In the component you then query the ContentChildren by the directive and get all the directives which gives you access to the column names and templates.

Here is the updated component:

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

}

Here is a slightly different way to do it maybe you like this more. I will now base it on your custom table sample since you provided more information.

You can create a directive that takes content and you specify the template as the content. Here is a sample implementation:

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

Then your parent component template will change to this:

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

Your custom table component needs to be changed. instead of receiving the templateNameList it needs to generate it from the ContentChildren on demand.

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

If you don't like this second approach you can still use what I suggested in the original sample in the same way. The only difference is how it looks in the template. I created also a StackBlitz sample so you can see it in practice.

这篇关于使用 angular 5 中的 contentChildren 获取多个 ng-template ref 值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆