Связывание событий при использовании 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 возвращает данные обратно в `