Binding events when using a ngForTemplate in Angular 2
假设我有这个简单的列表呈现组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import {Input, Component } from 'angular2/core' @Component({ selector: 'my-list', template: ` {{item}} ` }) class MyList { @Input() items: string[]; onItemClicked(item) { console.log('Item clicked:', item); } } |
我这样使用它:
1 | <my-list [items]='myAppsItems'></my-list> |
到现在为止还挺好。
接下来,我决定希望用户能够为渲染的项目提供自己的模板,因此我更改了组件
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Component({ selector: 'my-list', template: ` <template ngFor [ngForOf]="items" [ngForTemplate]="userItemTemplate" (click)='onItemClicked(item)'> </template> ` }) class MyList { @Input() items: string[]; @ContentChild(TemplateRef) userItemTemplate: TemplateRef; onItemClicked(item) { console.log('Item clicked:', item); } } |
并像这样使用它:
1 2 3 4 5 | <my-list [items]='items'> <template #item> item: {{item}} </template> </my-list> |
这仅适用于我不将任何事件处理程序绑定到列表项(插件)的情况。 如果像我在组件的第一个版本中那样尝试绑定到click事件,Angular会引发以下异常:
1 | "Event binding click not emitted by any directive on an embedded template" |
这是一个显示这个的小矮人。 您可以删除点击绑定,它会起作用。
我该如何解决? 我只希望用户能够为要通过ngFor进行迭代的下级项目指定模板,但是我需要能够将处理程序绑定到这些项目。
一直在寻找答案的一个星期,我终于想出了一个不错的解决方案。 我建议不要使用ngForTemplate,而不要使用ngTemplateOutlet。
已经在这里很好地描述了它:
angular2将数据从[ngTemplateOutlet]反馈回<模板>
列表项的定制模板位于组件标签之间:
1 2 3 4 5 | <my-list> <template let-item="item"> Template for: {{item.text}} ({{item.id}}) </template> </my-list> |
和组件模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <ul> <li *ngFor="let item of listItems" (click)="pressed(item)"> <template [ngTemplateOutlet]="template" [ngOutletContext]="{ item: item }"> </template> </li> </ul> |
我在这里举了一个例子:https://plnkr.co/edit/4cf5BlVoqzZdUQASVQaC?p=preview
项目模板是在App上下文中定义的,尚不清楚如何将其附加到"我的列表"组件上下文中。 我有处理模板及其变量的create wrapper指令,该指令包装在div中以捕获事件。 可以这样使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | @Directive({ selector: '[ngWrapper]' }) export class NgWrapper { @Input() private item:any; private _viewContainer:ViewContainerRef; constructor(_viewContainer:ViewContainerRef) { this._viewContainer = _viewContainer; } @Input() public set ngWrapper(templateRef:TemplateRef) { var embeddedViewRef = this._viewContainer.createEmbeddedView(templateRef); embeddedViewRef.setLocal('item', this.item) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | @Component({ selector: 'my-list', directives: [NgWrapper], template: ` <template ngFor #item [ngForOf]="items"> <template [ngWrapper]="userItemTemplate" [item]="item"></template> </template> ` }) class MyList { @Input() items: string[]; @ContentChild(TemplateRef) userItemTemplate: TemplateRef; userItemTemplate1: TemplateRef; onItemClicked(item) { console.log('Item click:', item); } ngAfterViewInit(){ this.userItemTemplate; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Component({ selector: 'my-app', directives: [MyList], template: ` <my-list [items]='items'> <template #item="item"> item: {{item}} </template> </my-list> ` }) export class App { items = ['this','is','a','test'] onItemClicked(item) { console.log('Item click:', item); } } |
解决方案不是完美的,但几乎是好的,请检查plunkr。