javascript - 在 Angular 2 上使用 *ngFor 在多行中显示来自 @ContentChildren 的组件内容

标签 javascript angular data-binding transclusion ngfor

我正在尝试使用 Angular 2 编写一个具有多列的简单 ListView ( GridView ?),我可以通过这种方式实例化:

<file-list [items]="items">
    <file-list-column title="Id" field="id"></file-list-column>
    <file-list-column title="Name" field="name"></file-list-column>
</file-list>

我实际上设法以一种非常黑客的方式完成这项工作,它生成一个包含我在 FileListColumn 组件中设置的信息数据的表格。

这就是我现在所拥有的:

FileListColumn 组件,其中定义列元数据:

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

@Component({
    selector: 'file-list-column',
    template: ''
})
export class FileListColumn {
    @Input()
    public title: string;
    @Input()
    public field: string;

    ngOnInit() {
    }
}

FileList组件,这是 ListView 的主要组件:

import {Component, Input, ContentChildren, QueryList, AfterContentInit, forwardRef} from '@angular/core';
import {FileListColumn} from './file.list.column';
import {IItem} from 'models';
@Component({
    selector: 'file-list',
    template: require('./file.list.html')
})
export class FileList implements AfterContentInit {
    @ContentChildren(forwardRef(() => FileListColumn))
    public columns: QueryList<FileListColumn>;

    @Input()
    public items: IItem[];

    constructor() {
    }

    public ngAfterContentInit() {
    }

    public getProperty(item:{[key:string]:any}, name:string) {
        return item[name];
    }
}

这是 FileList 的 View 组件:

<table>
    <thead>
        <tr>
            <th *ngFor="let col of columns">
                {{col.title}}
            </th>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor="let item of items">
            <td *ngFor="let col of columns">
                {{getProperty(item, col.field)}}
            </td>
        </tr>
    </tbody>
</table>

我提到的最奇怪的事情是,它的工作方式是,对于每个项目的每一列,它唯一做的就是调用 getProperty 函数传递项目和字段名称并返回该字段的值,这是一个不太理想的解决方案。

我现在想要做的是按照如下所示的方式在我的列中设置内容,并将该内容显示在我的表格中:

<file-list [items]="items">
    <file-list-column title="Id" field="id"></file-list-column>
    <file-list-column title="Name">
        <span>{{$item.name}}</span>
    </file-list-column>
    <file-list-column title="Some other column">
        <some-component item="$item"></some-component>
    </file-list-column>
</file-list>

不仅如此,我还希望能够为 FileListColumn 中的列定义 html组件本身以使代码更有条理。

我尝试过的事情之一是在 FileListColumn 的 View 中添加模板。组件:

<template #columnTemplate>
    <span>text to see if worked</span>
    <ng-content></ng-content>
</template>

然后我尝试在FileListColumn中获取它的引用组件具有: @ViewChild('columnTemplate') columnTemplate: TemplateRef<any>;

然后我尝试在 FileList 的 View 中加载它组件使用:

<td *ngFor="let col of columns" *ngForTemplate="col.columnTemplate">
</td>

但是我在浏览器上收到错误消息:

Error: Template parse errors:(…) "Error: Template parse errors:
Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * ("
    <tbody>
        <tr *ngFor="let item of items">
            <td *ngFor="let col of columns" [ERROR ->]*ngForTemplate="col.columnTemplate">
            </td>
        </tr>
"): FileList@10:44

即使这有效,我也不知道如何将我的 items 数组中的项目实际绑定(bind)到 FileListColumn内容。

有人知道我如何实现这些目标吗?

我一整天都在寻找解决这个问题的方法,但找不到任何有用的东西,大多数教程都是无关紧要的,因为它们是使用旧 API 为 Angular 2 的旧测试版编写的。

提前感谢您的帮助,并对冗长的帖子表示歉意。

更新1:

所以,我设法从FileListColumn加载模板。 <td>里面在 FileList使用 #columnTemplate 尝试根据 Günter Zöchbauer 的链接之一执行此操作:

<tr *ngFor="let item of items">
    <td *ngFor="let col of columns">
        <template [ngTemplateOutlet]="col.columnTemplate" [ngOutletContext]="item"></template>
    </td>
</tr>

模板已加载到每个单元格内,但仍然存在两个问题:

  • 我可以从模板访问的唯一属性是列属性,我无法从外部访问项目属性 ngFor .
  • 当我将内容添加到 FileListColumn 时通过这样做<file-list-column><div>Content here</div></file-list-column>加载于 <ng-content>在列模板中,此内容仅显示在最后行中,这意味着它不会在所有行中重复。

最佳答案

因此,我根据 Günter Zöchbauer 的答案中的链接找到了问题的解决方案。

FileList组件我添加了 <template>元素从我的 FileListColumn 加载模板成分。现在看起来像这样:

<tr *ngFor="let item of items">
    <td *ngFor="let col of columns">
        <template [ngTemplateOutlet]="col.columnTemplate" [ngOutletContext]="{ item: item, column: col }"></template>
    </td>
</tr>

如您所见,我通过了 item作为ngOutletContext这样我就可以在模板中访问它。

现在,这是 FileListColumn 的模板内容看起来像:

<template #internalColumnTemplate let-item="item">
    <span *ngIf="field">{{item[field]}}</span>
    <template *ngIf="externalTemplate" [ngTemplateOutlet]="externalTemplate" [ngOutletContext]="{ item: item }"></template>
</template>

它有 #internalColumnTemplate我可以在代码和 let-item="item" 中引用选择器它引用了我在 ngOutletContext 中传递的项目并使其在模板内部可用。 所以基本上,如果 field属性设置后,它显示span与 与字段值,否则,如果 externalTemplate已设置,这是我在 FileListColumn 中设置的另一个属性组件,它显示我在 FileListColumn 的定义中传递的任何模板.

这是完整的解决方案:

FileList组件file.list.ts:

import {Component, Input, ContentChildren, QueryList, AfterContentInit, forwardRef} from '@angular/core';
import {FileListColumn} from './file.list.column';
import {IItem} from 'models';

@Component({
    selector: 'file-list',
    template: require('./file.list.html')
})
export class FileList implements AfterContentInit {
    @ContentChildren(forwardRef(() => FileListColumn))
    public columns: QueryList<FileListColumn>;

    @Input()
    public items: IItem[];

    constructor() {
    }

    public ngAfterContentInit() {
    }
}

FileList模板file.list.html:

<table>
    <thead>
        <tr>
            <th *ngFor="let col of columns">
                {{col.title}}
            </th>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor="let item of items">
            <td *ngFor="let col of columns">
                <template [ngTemplateOutlet]="col.columnTemplate" [ngOutletContext]="{ item: item }"></template>
            </td>
        </tr>
    </tbody>
</table>

FileListColumn组件file.list.component.ts:

import {Component, Input, TemplateRef, ViewChild, ContentChild} from '@angular/core';

@Component({
    selector: 'file-list-column',
    template: require('./file.list.column.html')
})
export class FileListColumn {
    @Input()
    public title: string;
    @Input()
    public field: string;

    @ContentChild(TemplateRef)
    public externalTemplate: TemplateRef<any>;

    @ViewChild('internalColumnTemplate') 
    public columnTemplate: TemplateRef<any>;

    ngOnInit() {
    }
}

externalTemplate属性是您在使用 FileList 时设置的可选模板组件,而columnTemplateFileListColumn View 中的内部模板如前文和下文所示。

FileListColumn模板file.list.column.html:

<template #internalColumnTemplate let-item="item">
    <span *ngIf="field">{{item[field]}}</span>
    <template *ngIf="externalTemplate" [ngTemplateOutlet]="externalTemplate" [ngOutletContext]="{ item: item }"></template>
</template>

以下是控件 FileList 组件在设置后的使用方式:

<div>
    <file-list [items]="fakeItems">
        <file-list-column title="Id">
            <template let-item="item">
                <div>external {{item.id}}{{item.name}}</div>
            </template>
        </file-list-column>
        <file-list-column title="Name" field="name"></file-list-column>
    </file-list>
</div>

如您所见,您可以选择仅传递要使用的字段,列将解析它,或者您可以设置 <template>对于包含您要使用的任何 html 的列,不要忘记添加 let-item="item"如上所示,您可以访问该项目。

基本上就是这样。我希望这对其他人有用。

如果您有任何问题或改进建议,请告诉我,我会更新。

顺便说一句,我使用的是Angular 2.0.1

关于javascript - 在 Angular 2 上使用 *ngFor 在多行中显示来自 @ContentChildren 的组件内容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39847450/

相关文章:

c# - 如何测试WinForm DataBinding?

c# - 单击按钮更新数据时 GridView 刷新

javascript - Youtube iFrame嵌入式视频跟踪-Google跟踪代码管理器

Javascript 匿名函数与无函数

angular - 使用 NgRx 时如何避免不必要的 API 调用

forms - Angular 2 : Can't bind to 'ngModel' since it isn't a known property of 'input'

WPF datagrid 隐藏列与动态数据源

javascript - 通过 id 获取元素,然后获取 class 属性

javascript - NativeScript 中的 Accordion

Angular 2在模态窗口中获取路由器参数