首页 > 解决方案 > Angular 8 - 如何识别(和订阅)模板中传递给组件的元素

问题描述

我有一个渲染“项目”组件的父组件,使用模板来定义项目的标记(我已经将我的场景剥离到裸露的骨头):

// parent component template

<item [model]="itemModel" [template]="itemTemplate"></item>

<ng-template #itemTemplate let-model="model">
    <div>AAA {{model.property1}}</div>
    <div>BBB {{model.property2}}</div>
    <div>CCC {{model.property3}}</div>
</ng-template>
// "item" component template

<ng-container [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{model: model}"></ng-container>

在“item”组件代码中,我需要订阅#itemTemplate 中的一个元素(例如“AAA”div)的点击事件。问题是它是父组件定义了#itemTemplate 中的哪些元素将被订阅。这可以由开发人员在父组件中硬编码(如何准确地做到这一点是这里问题的一部分)。同时,点击处理程序需要在“item”组件内部(封装点击处理功能——父组件不需要关心)。

我尝试了三种方法来做到这一点,但我都不喜欢,我想知道是否有更好的方法。这 3 次尝试是相互独立的。

1.使用模板引用变量

这个想法:就像#itemTemplate 引用用于标识模板一样,使用相同的语法来创建对所需组件的引用(要订阅)。然后通过新的@Input 将引用传递给“item”组件。

// parent component template
// the "item" component now takes an additional input "subscribeToThis"

<item [model]="itemModel" [template]="itemTemplate" [subscribeToThis]="subscribeToThis"></item>

<ng-template #itemTemplate let-model="model">

    // the #subscribeToThis reference on the div below is used above as the value
    // for the subscribeToThis input of the child component;
    // but it's always undefined ...

    <div #subscribeToThis >AAA {{model.property1}}</div>
    <div>BBB {{model.property2}}</div>
    <div>CCC {{model.property3}}</div>
</ng-template>

结果/问题:在“item”组件中,subscribeToThis 是未定义的。(我认为它是未定义的,因为 #subscribeToThis 引用的范围 - 我是对的吗?)因此“item”组件不能使用它来查找要订阅的元素。

与此类似,结果相同 - 我尝试在“item”组件类中找到#subscribeToThis 引用,如下所示:

// bits from child component class

@ViewChild('subscribeToThis', {static: false}) elementToSubscribeTo: TemplateRef<any>;

  ngAfterViewInit() {

    // this.elementToSubscribeTo is undefined here;
    // if "things worked", this.elementToSubscribeTo would refer to the element to whose click event I need to subscibe to

  }

但 elementToSubscribeTo 再次未定义。

2.使用css类标记和查找元素

想法:为所需的#itemTemplate 元素分配一个特殊的类(在下面的示例中为“subscribe-to-this”):

// parent component template
<ng-template #itemTemplate let-model="model">

    // note the class on this div; you can use low-level tools to find the element
    // by its class, but that's not "clean angular"

    <div class="subscribe-to-this">AAA {{model.property1}}</div>
    <div>BBB {{model.property2}}</div>
    <div>CCC {{model.property3}}</div>
</ng-template>

然后,在“item”组件中,将 ElementRef 和 Renderer2 注入其中。使用它们,我可以按类查找元素,然后订阅其单击事件。

结果/成功: “item”组件将找到元素,并且订阅元素的 click 事件会触发“item”组件中的点击处理程序。

这种方法的问题:有充分的理由不鼓励这种低级方法。

3.直接在模板中的元素中分配点击处理程序

想法:

// parent component template
<ng-template #itemTemplate let-model="model">

    // the click handler is assigned right here in the template;
    // note that the event handler is a function on the model
    // that is passed to the child component; that function needs to be
    // added to the model "artificially" since the model shouldn't need to be aware
    // of how it's handled in the "item" component; that's one of the bad points
    // in this attempt

    <div (click)="model.clickHandler()">AAA {{model.property1}}</div>
    <div>BBB {{model.property2}}</div>
    <div>CCC {{model.property3}}</div>
</ng-template>

为此,必须将 clickHandler() 函数添加到模型中(例如,在“item”组件的 onNgInit 中)。这种体操的原因是#itemTemplate 中的用法相对简单。

结果/成功:单击正确的元素时调用 clickHandler()。

这种方法的问题:

结论

我是 Angular 的新手,我希望我只是缺少一些技术,可以让我干净地实现这一点,同时保持“项目”组件尽可能“黑盒子”。感谢您的任何想法或见解。

标签: angulardom-eventsangular2-template

解决方案


您只需要在上下文中提供 click 方法,例如

// "item" component template
<ng-container [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{model: model, onClickItem: doStuffWhenClick}"></ng-container>
// "item" component ts file
public doStuffWhenClick(...) { ... }
// parent component template

<item [model]="itemModel" [template]="itemTemplate"></item>

<ng-template #itemTemplate let-model="model" let-onClickItem="onClickItem">
    <div (click)="onClickItem()">AAA {{model.property1}}</div>
    <div>BBB {{model.property2}}</div>
    <div>CCC {{model.property3}}</div>
</ng-template>

推荐阅读