Bindon: малоизвестные фишки шаблонов Angular

от автора

Недавно вышел Angular 12, а вместе с ним в шаблоны подвезли оператор нулевого слияния (??). Но что еще умеют шаблоны Angular, о чем вы, возможно, и не слышали? Давайте разберемся!

ngProjectAs

Проекция контента в Angular похожа на систему слотов Web Components. Вы можете написать просто <ng-content></ng-content> — и все, что вы поместите внутрь вашего компонента, будет спроецировано туда.

Вы также можете добавить несколько тэгов контента и проецировать отдельные части содержимого в разные места через атрибут select, как на примере ниже:

@Component({   selector: 'layout',   template: `     <ng-content select="header"></ng-content>     <main>       <ng-content select="aside"></ng-content>       <ng-content></ng-content>     </main>     <ng-content select="footer"></ng-content>   `,   styles: [     `       :host {         height: 100%;         display: flex;         flex-direction: column;       }         main {         display: flex;         flex: 1;       }     `   ] }) export class LayoutComponent {}

Такому компоненту можно передать содержимое следующим образом:

<layout>   <header>Header</header>   <aside>Sidebar</aside>   <footer>Footer</footer>   I am content </layout>

Но что, если вы хотите спроецировать текст, не оборачивая его DOM-элементом? Или, например, поместить блок из нескольких элементов в один слот? Тогда можно использовать тэг ng-container, пометив его атрибутом ngProjectAs. Вот пример:

https://stackblitz.com/edit/angular-content-selection

Совсем недавно на angular.io добавили отдельную статью о проецировании контента.

ngNonBindable

Допустим, вы хотите вывести на страницу пример интерполяции фигурными скобками, показав как есть: <div>Hello, {{userName}}</div>. Но, если вы хотите вывести непосредственно фигурные скобки, их нужно экранировать. Иначе Angular попытается их обработать. Добавьте одну { в шаблон. Вы увидите следующее сообщение:

Do you have an unescaped «{» in your template? Use «{{ ‘{‘ }}») to escape it.

Писать пять скобок вместо одной — это перебор. Возможно, вы подумали про изменение символа интерполяции для этого компонента, но это не поможет. Свой символ интерполяции только добавляет альтернативу, а не заменяет символ по умолчанию.

На помощь приходит ngNonBindable. Это директива компилятора (как i18n), которая говорит ему относиться к этому куску как к простому HTML: <div ngNonBindable>Hello, {{userName}}</div>

ngPreserveWhitespaces

Как вы, наверное, знаете, Angular избавляет вас от головной боли пустых кусков в разметке, удаляя их при компиляции. Эту штуку можно отключить в angularCompilerOptions в tsconfig.json для всего проекта или в декораторе @Component для конкретного компонента.

В целом это довольно полезная фишка, но иногда может быть необходимо отключить ее на небольшом куске шаблона. Еще одна директива компилятора может помочь с этим: просто поместите ngPreserveWhitespaces на элемент — и компилятор оставит все пробелы внутри в целости.

&ngsp;

В продолжение прошлого пункта: символ неразрывного пробела компилятор считает за пробел и удаляет. Если вы хотите его сохранить, есть специальный символ, хотите верьте, хотите нет, пришедший из Angular Dart&ngsp;. Он переживет чистку компилятора и будет заменен на обычный &nbsp;.

$any()

Вам когда-то попадались библиотеки с кривыми типами? Например, вы оперируете немутабельными данными и хотите передать в инпут компонента массив. Но автор библиотеки указал в качестве типа обычный массив. TypeScript выдаст вам ошибку, указав, что ваш ReadOnly-массив не имеет мутабельных методов и не может быть передан в компонент. Или бывает, что указанный интерфейс в принципе некорректный, если мейнтейнер библиотеки не фанат TypeScript. В таком случае есть два действия, которые нужно предпринять:

  1. Обернуть ваш объект в специальную функцию $any: [value]="$any(value)".

  2. Пойти в GitHub библиотеки и завести PR, исправляющий типы.

Не забудьте про второй пункт, без него ничего не сработает!

Bindon

У вас не рябит в глазах от обилия специальных символов?

<my-component   #component   [@animation]=”animation”   [value]=”value”   [(banana)]=”twoWayBoundValue”   (output)=”onOutput($event)”   (click)=”onClick()” ></my-component>

А ведь есть альтернативный синтаксис, с помощью которого тот же шаблон можно записать без всех этих скобок:

<my-component   ref-component   bind-animate-animation=”animation”   bind-value=”value”   bindon-banana=”twoWayBoundValue”   on-output=”onOutput($event)”   on-click=”onClick()” ></my-component>

Честно говоря, не знаю, зачем так делать. Может быть, чтобы было легко сортировать байндинги по типу? В любом случае теперь вы тоже так умеете.

Бонус

А вы знали, что, кроме ng-template, ng-container и ng-content, есть еще один встроенный тэг: ng-component. Это не совсем фишка шаблонов, но я впервые увидел его, изучая шаблон в DevTools, и меня эта находка удивила, так что я решил включить ее в статью.

Может быть, вы догадаетесь, отчего он появляется в HTML? Небольшая подсказка: селектор в @Component необязателен.

@Component({    selector: 'app-root', // ←---- это поле опционально    ... })

Что же Angular может использовать для компонентов без селектора? Именно ng-component — автоматически созданный под них тэг. Но как компонент без селектора может оказаться в разметке? Его можно динамически создать с помощью *ngComponentOutlet или его может вставить Router.

Знаете ли вы еще какие-то особенные, плохо задокументированные возможности шаблонов, которые я не упомянул? Поделитесь ими в комментариях! А то я нигде не встречал полного списка и собрал все это из личного опыта и путешествий по исходникам.

ссылка на оригинал статьи https://habr.com/ru/company/tinkoff/blog/564622/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *