Bindon: малоизвестные фишки шаблонов Angular
Недавно вышел Angular 12, а вместе с ним в шаблоны подвезли оператор нулевого слияния (??
). Но что еще умеют шаблоны Angular, о чем вы, возможно, и не слышали? Давайте разберемся!
ngProjectAs
Проекция контента в Angular похожа на систему слотов Web Components. Вы можете написать просто
— и все, что вы поместите внутрь вашего компонента, будет спроецировано туда.
Вы также можете добавить несколько тэгов контента и проецировать отдельные части содержимого в разные места через атрибут select
, как на примере ниже:
@Component({
selector: 'layout',
template: `
`,
styles: [
`
:host {
height: 100%;
display: flex;
flex-direction: column;
}
main {
display: flex;
flex: 1;
}
`
]
})
export class LayoutComponent {}
Такому компоненту можно передать содержимое следующим образом:
Header
I am content
Но что, если вы хотите спроецировать текст, не оборачивая его DOM-элементом? Или, например, поместить блок из нескольких элементов в один слот? Тогда можно использовать тэг ng-container
, пометив его атрибутом ngProjectAs
. Вот пример:
https://stackblitz.com/edit/angular-content-selection
Совсем недавно на angular.io добавили отдельную статью о проецировании контента.
ngNonBindable
Допустим, вы хотите вывести на страницу пример интерполяции фигурными скобками, показав как есть:
. Но, если вы хотите вывести непосредственно фигурные скобки, их нужно экранировать. Иначе Angular попытается их обработать. Добавьте одну {
в шаблон. Вы увидите следующее сообщение:
Do you have an unescaped »{» in your template? Use »{{ '{' }}») to escape it.
Писать пять скобок вместо одной — это перебор. Возможно, вы подумали про изменение символа интерполяции для этого компонента, но это не поможет. Свой символ интерполяции только добавляет альтернативу, а не заменяет символ по умолчанию.
На помощь приходит ngNonBindable
. Это директива компилятора (как i18n
), которая говорит ему относиться к этому куску как к простому HTML:
ngPreserveWhitespaces
Как вы, наверное, знаете, Angular избавляет вас от головной боли пустых кусков в разметке, удаляя их при компиляции. Эту штуку можно отключить в angularCompilerOptions
в tsconfig.json
для всего проекта или в декораторе @Component для конкретного компонента.
В целом это довольно полезная фишка, но иногда может быть необходимо отключить ее на небольшом куске шаблона. Еще одна директива компилятора может помочь с этим: просто поместите ngPreserveWhitespaces
на элемент — и компилятор оставит все пробелы внутри в целости.
&ngsp;
В продолжение прошлого пункта: символ неразрывного пробела компилятор считает за пробел и удаляет. Если вы хотите его сохранить, есть специальный символ, хотите верьте, хотите нет, пришедший из Angular Dart — &ngsp;
. Он переживет чистку компилятора и будет заменен на обычный
.
$any ()
Вам когда-то попадались библиотеки с кривыми типами? Например, вы оперируете немутабельными данными и хотите передать в инпут компонента массив. Но автор библиотеки указал в качестве типа обычный массив. TypeScript выдаст вам ошибку, указав, что ваш ReadOnly-массив не имеет мутабельных методов и не может быть передан в компонент. Или бывает, что указанный интерфейс в принципе некорректный, если мейнтейнер библиотеки не фанат TypeScript. В таком случае есть два действия, которые нужно предпринять:
Обернуть ваш объект в специальную функцию
$any
:[value]="$any(value)"
.Пойти в GitHub библиотеки и завести PR, исправляющий типы.
Не забудьте про второй пункт, без него ничего не сработает!
Bindon
У вас не рябит в глазах от обилия специальных символов?
А ведь есть альтернативный синтаксис, с помощью которого тот же шаблон можно записать без всех этих скобок:
Честно говоря, не знаю, зачем так делать. Может быть, чтобы было легко сортировать байндинги по типу? В любом случае теперь вы тоже так умеете.
Бонус
А вы знали, что, кроме ng-template
, ng-container
и ng-content
, есть еще один встроенный тэг: ng-component
. Это не совсем фишка шаблонов, но я впервые увидел его, изучая шаблон в DevTools, и меня эта находка удивила, так что я решил включить ее в статью.
Может быть, вы догадаетесь, отчего он появляется в HTML? Небольшая подсказка: селектор в @Component
необязателен.
@Component({
selector: 'app-root', // ←---- это поле опционально
...
})
Что же Angular может использовать для компонентов без селектора? Именно ng-component
— автоматически созданный под них тэг. Но как компонент без селектора может оказаться в разметке? Его можно динамически создать с помощью *ngComponentOutlet
или его может вставить Router.
Знаете ли вы еще какие-то особенные, плохо задокументированные возможности шаблонов, которые я не упомянул? Поделитесь ими в комментариях! А то я нигде не встречал полного списка и собрал все это из личного опыта и путешествий по исходникам.