【问题标题】:Display component's content from @ContentChildren in multiple rows using *ngFor on Angular 2在 Angular 2 上使用 *ngFor 在多行中显示来自 @ContentChildren 的组件内容
【发布时间】:2017-02-12 08:00:03
【问题描述】:

我正在尝试使用 Angular 2 编写一个包含多个列的简单列表视图(网格视图?),我可以通过这种方式实例化:

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

我实际上设法以一种非常 hacky 的方式完成了这项工作,它生成了一个包含我在 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 组件,这是列表视图的主要组件:

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组件的视图:

<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 组件的视图中添加一个模板:

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

然后我尝试在FileListColumn 组件中获取它的引用: @ViewChild('columnTemplate') columnTemplate: TemplateRef&lt;any&gt;;

然后我尝试在FileList 组件的视图中使用:

<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 数组中的项目实际绑定到 FileListColumn 内容。

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

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

在此先感谢您的帮助,很抱歉发了这么长的帖子。

更新 1:

因此,我根据 Günter Zöchbauer 的链接之一尝试使用 #columnTemplate 尝试从 &lt;td&gt; 内部的 FileListColumn 加载模板:

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

模板在每个 cel 中加载,但仍然存在两个问题

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

【问题讨论】:

    标签: javascript angular data-binding transclusion ngfor


    【解决方案1】:

    你可以这样使用ngForTemplate

    <template ngFor let-col [ngForOf]="colums" [ngForTemplate]="col?.columnTemplate">
      <td></td>
    </template>
    

    我不知道这是否能解决你所有的问题。

    这可能也行(我自己没试过)

    <td *ngFor="let col of columns template:col?.columnTemplate">
    </td>
    

    其实我试过了:-/Repeating use of ng-content。也许发生了一些改变,打破了这种方法。

    另见

    【讨论】:

    • 好的,第一个给我Cannot read property 'columnTemplate' of undefined,如果我从ngForTemplate 中删除col.,我将停止收到错误,但没有加载任何内容。第二个给了我与第一个相同的错误,这很奇怪,因为我检查了columnTemplate 有模板,如果我删除模板并将{{col.field}}放在 中,则会显示字段文本。
    • 顺便说一句,这些尝试在FileList 组件的模板中,对于我的用例来说,将
    • 我认为您需要一个额外的?。请参阅我的更新答案。
    • 不是真的,我实际上找到了解决问题的方法,我会将它作为我自己问题的答案发布,它基于您发送的两个链接。非常感谢您的帮助!
    【解决方案2】:

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

    FileList 组件中,我添加了一个&lt;template&gt; 元素以从我的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 选择器和引用我在ngOutletContext 中传递的项目的let-item="item" 并使其在模板中可用。 所以基本上,如果设置了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 视图中的内部模板,如下所示。

    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>
    

    如您所见,您可以选择只传递您要使用的字段,然后列将解析它,或者您可以使用您要使用的任何 html 为列设置 &lt;template&gt;,并且不要不要忘记添加let-item="item",如上所示,以便您可以访问该项目。

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

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

    顺便说一句,我正在使用 Angular 2.0.1

    【讨论】:

      猜你喜欢
      相关资源
      最近更新 更多
      热门标签