Связывание событий при использовании ngForTemplate в Angular 2
Допустим, у меня есть этот простой компонент рендеринга списка:
import {Input, Component } from 'angular2/core'
@Component({
selector: 'my-list',
template: `
<div *ngFor='#item of items' (click)='onItemClicked(item)'>
{{item}}
</div>
`
})
class MyList {
@Input() items: string[];
onItemClicked(item) { console.log('Item clicked:', item); }
}
Я использую это так:
<my-list [items]='myAppsItems'></my-list>
Все идет нормально.
Затем я решаю, что хочу, чтобы пользователь мог предоставить свой собственный шаблон для отображаемых элементов, поэтому я изменяю компонент
@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); }
}
И используйте это так:
<my-list [items]='items'>
<template #item>
<h1>item: {{item}}</h1>
</template>
</my-list>
Это работает, только я не привязываю никакие обработчики событий к элементам списка ( plunker). Если я пытаюсь привязаться к событию click, как я это делал в первой версии компонента, Angular выдает следующее исключение:
"Event binding click not emitted by any directive on an embedded template"
Вот плункер, показывающий это. Вы можете удалить привязку клика, и она будет работать.
Как это исправить? Я просто хочу, чтобы пользователь мог указать шаблон для подчиненного элемента, который я собираюсь повторить через ngFor, но мне нужно иметь возможность привязывать обработчики к этим элементам.
2 ответа
Шаблон элемента определяется в контексте приложения, не ясно, как его прикрепить к контексту компонента my-list. У меня есть директива create wrapper, которая обрабатывает шаблон и его переменные, директива оборачивается в div для захвата событий. Это можно использовать так:
@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)
}
}
@Component({
selector: 'my-list',
directives: [NgWrapper],
template: `
<template ngFor #item [ngForOf]="items">
<div (click)="onItemClicked(item)">
<template [ngWrapper]="userItemTemplate" [item]="item"></template>
</div>
</template>
`
})
class MyList {
@Input() items: string[];
@ContentChild(TemplateRef) userItemTemplate: TemplateRef;
userItemTemplate1: TemplateRef;
onItemClicked(item) {
console.log('Item click:', item);
}
ngAfterViewInit(){
this.userItemTemplate;
}
}
@Component({
selector: 'my-app',
directives: [MyList],
template: `
<my-list [items]='items'>
<template #item="item">
<h1>item: {{item}}</h1>
</template>
</my-list>
`
})
export class App {
items = ['this','is','a','test']
onItemClicked(item) {
console.log('Item click:', item);
}
}
Решение не префектное, но почти хорошее, проверьте plunkr.
Я искал ответ на этот вопрос уже неделю, и я наконец-то нашел довольно приличное решение. Вместо использования ngForTemplate я бы предложил использовать ngTemplateOutlet.
Это уже хорошо описано здесь: angular2 возвращает данные обратно в `` из `[ngTemplateOutlet]`
Пользовательский шаблон для элементов списка помещается между тегами компонента:
<my-list>
<template let-item="item">
Template for: <b>{{item.text}}</b> ({{item.id}})
</template>
</my-list>
И шаблон компонента:
<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