在Angular 2中使用ngForTemplate时的绑定事件

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。