У меня есть родительский компонент, который визуализирует компонент «item», используя шаблон для определения разметки элемента (я разобрал свой сценарий до пустых костей):
// 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", мне нужно подписаться на событие click одного из элементов внутри #itemTemplate (например, div "AAA"). Подвох в том, что именно родительский компонент определяет, на какой из элементов в #itemTemplate следует подписаться. Это может быть жестко задано разработчиком в родительском компоненте (как именно это сделать, является частью вопроса здесь). В то же время обработчик кликов должен находиться внутри компонента item (для инкапсуляции функциональности обработки кликов - родительский компонент не должен этим заниматься).
Я попробовал три способа, как это сделать. это, но я тоже не люблю, и мне интересно, есть ли лучший способ. Эти 3 попытки не зависят друг от друга.
1. используйте переменную шаблона ref
Идея: , так же как ссылка #itemTemplate используется для идентификации шаблона, используйте тот же синтаксис, чтобы создать ссылку на нужный компонент (для подписки). Затем передайте ссылку на компонент "item" через новый @ Input.
// 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" не может использовать это, чтобы найти элемент для подписки.
Аналогично этому, с тем же результатом - я попытался найти ссылку #subscribeToThis в классе компонентов "item" следующим образом:
// 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:
// 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. Используя их, я могу найти элемент по классу, а затем подписаться на его событие click.
Результат / успех: компонент "item" найдет элемент и подписку на щелчок элемента событие вызывает триггер обработчика щелчка в компоненте "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 () в модель (например, в onNgInit компонента "item"). Причиной такой гимнастики является относительно простое использование в # itemTemplate.
Результат / успех: clickHandler () вызывается при нажатии на правильный элемент.
Проблемы с этим подходом:
Родительский компонент должен знать имя функции magi c. Это не прерыватель сделки, но это шаг от красивого чистого черного ящика.
Если потребитель (родитель) должен работать с данными itemModel впоследствии, " функция надстройки clickHandler () не очень удобна. Неприятно, когда родитель возвращает данные с добавленным «мусором». Я знаю, что могу удалить его из объекта, но это похоже на начало кроличьей норы.
Заключение
Я новичок в Angular, и я надеясь, что мне просто не хватает какой-то техники, которая позволила бы мне реализовать это чисто, сохраняя компонент "item" как можно более "черным". Спасибо за любые идеи или идеи.