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.
В чём проблема
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/
Добавить комментарий