Angular: ng-content для ng-template

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Macros are comparable with functions in regular programming languages. They are useful to reuse template fragments to not repeat yourself.
Macros are defined in regular templates.

Twig 3.x documentation

В чём проблема

Content Projection - очень удобный инструмент организаци шаблонов. Тема неоднократно и хорошо разобрана на многочисленных ресурсах. Тем не менее, такой аспект как использование content projection совместно с ng-template удобной штатной реализации не имеет. С одной строны, это и не проблема совсем, поскольку сами компоненты c лихвой решают эту задачу. Но, если возникает необходимость и желание быть ближе к DRY без создания вспомогательных компонентов, то возможности, наподобие тех, что есть в Twig, Jinja2, Nunjucks и других шаблонизаторах весьма кстати.

Способы решения проблемы

<ng-template #tpl1 let-param let-mark="mark">
  <div>
    <ng-content></ng-content> <!-- Not works here -->
    <ng-container *ngTemplateOutlet="param"></ng-container>
      World {{mark}}        
  </div>
</ng-template>

<ng-template #paramTemplate1>
  Hello 
</ng-template>

<ng-template #paramTemplate2>
  Hi
</ng-template>

<ng-container *ngTemplateOutlet="tpl1; context: {$implicit: paramTemplate1, mark: '!'}">
</ng-container>
    
<ng-container *ngTemplateOutlet="tpl1; context: {$implicit: paramTemplate2, mark: '!!!'}">
</ng-container>

Это пример решения задачи с использованием стандартных возможностей. У него очевидные проблемы, связанные и с читабельностью разметки и с её семантикой. Лично мне весьма сложно понять кто что рендерит и что в итоге получится.

Представляя себе конечный результат, ожидаешь увидеть что-то вроде:

 <ng-template #tpl21 let-ctx>      
   <div>
     <ng-macro-content></ng-macro-content> 
     World {{ctx.mark}}       
   </div>
 </ng-template>

 <ng-macro [template]="tpl21" [context]="{ mark: '!' }">
   Hello
 </ng-macro>

 <ng-macro [template]="tpl21" [context]="{ mark: '!!!' }">
   Hi
 </ng-macro>

Для того, чтоб всё работало как ожидается, необходимо чтоб на уровне ng-macro произошел захват ссылки на шаблон (TemplateRef), и совместно с контекстом шаблона состояние было сохранено в дереве компонентов рендеринга (не совсем то же самое что и DI-иерархия). Соответственно, на уровне ng-macro-content необходимо это состояние извлечь, и отредерить. Первая задача решается тривильно, а с решением второй приходится схитрить, и воспользоваться классом ViewContainerRef , который, благо, хранит нужное нам состояние в private поле _hostLView типа LView.

Нотация разметки в виде тегов хорошо читаема, но побочным продуктом такого подхода является появление соответствующих узлов в DOM документа.

Это неудобство можно устранить переписав решение в нотации атрибутов, т.е. через структурные директивы:

<ng-template #tpl22 let-ctx>      
  <div>
    <span *ngMacroContent></span>
    World {{ctx.mark}}       
  </div>
</ng-template>

<ng-container *ngMacro="tpl22; context: { mark: '!' }" >
  Hello
</ng-container>

<span *ngMacro="tpl22; context: { mark: '!!!' }" >
  Hi
</span>

На мой взгляд, такая нотация выглядит и компактней и читабельней, и в результате получится:

Заключение

По причине использования закрытого API технически решение получилось не очень элегантное. По хорошему, такая возможность должа быть штатно (возможно, она и есть, но я про неё не знаю). Тем не менее, подход благополучно живёт в эксплуатации уже пару лет и не менее благополучно мигрирует со всеми обновлениями без потери работоспособности (иногда, с обновлениями фреймворка приходится вносить минимальные изменения). В целом, использование такого инструментария удобно. Фактически, ng-templatе в рамках шаблона становятся полноценными функциями высшего порядка, со всеми вытекающими из этого обстоятельства возможностями, поскольку появляется удобный инструментарий их вызова и композиции.

Если у кого-то есть идеи как реализовать решение штатными средствами, то буду благодарен за совет.

Пример на StackBlitz для Angular 14.

Источник: https://habr.com/ru/post/669656/


Интересные статьи

Интересные статьи

Говоря о разработке сайтов с использованием CMS 1C Bitrix вопрос покрытия тестами поднимается редко. Главная причина в том, что большинство проектов обходится штатным функционалом, который предоставля...
Всем привет. Если вы когда-либо работали с универсальными списками в Битрикс24, то, наверное, в курсе, что страница детального просмотра элемента полностью идентична странице редак...
В 1С Битрикс есть специальные сущности под названием “Информационные блоки, сокращенно (инфоблоки)“, я думаю каждый с ними знаком, но не каждый понимает, что это такое и для чего они нужны
Одной из «киллер-фич» 12й версии Битрикса была объявлена возможность отдавать статические файлы из CDN, тем самым увеличивая скорость работы сайта. Попробуем оценить практический выигрыш от использова...
Практически все коммерческие интернет-ресурсы создаются на уникальных платформах соответствующего типа. Среди них наибольшее распространение получил Битрикс24.