【问题标题】:Binding events when using a ngForTemplate in Angular 2在 Angular 2 中使用 ngForTemplate 时绑定事件
【发布时间】:2016-04-19 21:56:46
【问题描述】:

假设我有这个简单的列表渲染组件:

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"

这是plunker showing that。您可以删除点击绑定,它会工作。

我该如何解决这个问题?我只是希望用户能够为我将通过 ngFor 迭代的从属项指定一个模板,但我需要能够将处理程序绑定到这些项。

【问题讨论】:

  • 嗯,这是一个很好的...有一点值得注意 - 你的笨蛋都在使用 beta-8,它已经过时了大约 2 个月。更新到最新版本(beta-15)并不能修复错误,但我仍然建议这样做

标签: angular


【解决方案1】:

一个星期以来一直在寻找这个问题的答案,我终于想出了一个相当不错的解决方案。我建议不要使用 ngForTemplate,而是使用 ngTemplateOutlet。

这里已经很好地描述了: angular2 feeding data back to `<template>` from `[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

【讨论】:

  • 谢谢你!我刚刚更新了我的代码并摆脱了我的自定义模板渲染指令。仅供参考,您的代码中有两个 [ngOutletContext] 绑定。它之所以有效,是因为第二个被忽略了。
  • @Mud 不客气!我注意到如果不添加国际化 polyfill,它在 Android 股票浏览器或 Firefox (android) 中不起作用。否则会抛出错误“Intl is not defined”。
【解决方案2】:

Item 模板是在 App 上下文中定义的,不清楚如何将其附加到 my-list 组件上下文中。我创建了处理模板及其变量的包装指令,指令被包装到 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

【讨论】:

  • 请参阅stackoverflow.com/questions/37225722/… 了解如何使用context 而不是已删除的setLocal(基于这个出色的答案)
  • 很好,但是 RC4 失败了,请查看更新的 plunkr plnkr.co/edit/mFdVd9aORm2ciEfSni33?p=preview
  • 在 Angular 2 Final 发布之前,我不建议使用这种方法(或任何其他具有类似复杂性的方法)。 API 还不稳定,所以在不久的将来您可能会遇到更多问题。
  • 自上次发布 angular2 以来它发生了一些变化。 embeddedViewRef 中没有 setLocal 方法。您将上下文作为第二个参数传递。改变的另一件事是在模板元素上使用let-item 语法而不是#item
猜你喜欢
  • 2016-08-08
  • 1970-01-01
  • 1970-01-01
  • 2017-11-05
  • 2018-02-02
  • 2017-10-21
  • 2017-06-25
  • 2017-07-30
  • 1970-01-01
相关资源
最近更新 更多