Обзор проверки типов в шаблонах
Подобно тому, как TypeScript отлавливает ошибки типов в вашем коде, Angular проверяет выражения и привязки внутри
шаблонов вашего приложения и может сообщать о найденных ошибках типов.
В настоящее время в Angular существует три режима проверки, зависящих от значений флагов fullTemplateTypeCheck и
strictTemplates в опциях компилятора Angular.
Базовый режим
В самом базовом режиме проверки типов, когда флаг fullTemplateTypeCheck установлен в false, Angular проверяет только
выражения верхнего уровня в шаблоне.
Если вы напишете <map [city]="user.address.city">, компилятор проверит следующее:
userявляется свойством класса компонентаuser— это объект со свойствомaddressuser.address— это объект со свойствомcity
Компилятор не проверяет, можно ли присвоить значение user.address.city входному свойству city компонента <map>.
В этом режиме у компилятора также есть существенные ограничения:
- Важно отметить, что он не проверяет встроенные представления, такие как
*ngIf,*ngForи другие встроенные представления<ng-template>. - Он не определяет типы
#refs, результаты работы Pipe-ов или тип$eventв привязках событий.
Во многих случаях эти элементы получают тип any, что может привести к тому, что последующие части выражения останутся
без проверки.
Полный режим
Если флаг fullTemplateTypeCheck установлен в true, Angular выполняет более тщательную проверку типов внутри
шаблонов.
В частности:
- Проверяются встроенные представления (например, внутри
*ngIfили*ngFor) - Pipe-ы имеют правильный возвращаемый тип
- Локальные ссылки на директивы и Pipe-ы имеют правильный тип (за исключением любых обобщенных (generic) параметров,
которые будут
any)
Следующие элементы по-прежнему имеют тип any:
- Локальные ссылки на DOM-элементы
- Объект
$event - Выражения безопасной навигации (Safe navigation expressions)
ВАЖНО: Флаг fullTemplateTypeCheck устарел в Angular 13.
Вместо него следует использовать семейство опций компилятора strictTemplates.
Строгий режим
Angular сохраняет поведение флага fullTemplateTypeCheck и вводит третий «строгий режим» (strict mode).
Строгий режим является надстройкой над полным режимом и активируется установкой флага strictTemplates в true.
Этот флаг заменяет флаг fullTemplateTypeCheck.
В дополнение к поведению полного режима, Angular делает следующее:
- Проверяет, что привязки компонентов/директив могут быть присвоены их
input()-ам - Соблюдает флаг TypeScript
strictNullChecksпри проверке предыдущего режима - Выводит правильный тип компонентов/директив, включая дженерики
- Выводит типы контекста шаблона, где это настроено (например, позволяя корректно проверять типы в
NgFor) - Выводит правильный тип
$eventв привязках событий компонентов/директив, DOM и анимаций - Выводит правильный тип локальных ссылок на DOM-элементы на основе имени тега (например, тип, который вернул бы
document.createElementдля этого тега)
Проверка *ngFor
Три режима проверки типов по-разному обрабатывают встроенные представления. Рассмотрим следующий пример.
User interface
interface User {
name: string;
address: {
city: string;
state: string;
};
}
<div *ngFor="let user of users">
<h2>{{config.title}}</h2>
<span>City: {{user.address.city}}</span>
</div>
Теги <h2> и <span> находятся во встроенном представлении *ngFor.
В базовом режиме Angular не проверяет ни один из них.
Однако в полном режиме Angular проверяет существование config и user, предполагая тип any.
В строгом режиме Angular знает, что user внутри <span> имеет тип User, а address — это объект со свойством
city типа string.
Устранение ошибок шаблона
В строгом режиме вы можете столкнуться с ошибками шаблона, которые не возникали ни в одном из предыдущих режимов. Эти ошибки часто представляют собой действительные несоответствия типов в шаблонах, которые не были обнаружены предыдущими инструментами. В таком случае сообщение об ошибке должно четко указывать, где в шаблоне возникла проблема.
Также могут возникать ложные срабатывания, когда типизация библиотеки Angular неполная или некорректная, или когда типизация не совсем соответствует ожиданиям, как в следующих случаях:
- Когда типизация библиотеки неверна или неполна (например, отсутствует
null | undefined, если библиотека не была написана с учетомstrictNullChecks). - Когда типы входных данных библиотеки слишком узкие, и библиотека не добавила соответствующие метаданные, чтобы Angular
мог это понять.
Это обычно происходит с атрибутом
disabledили другими распространенными логическими (Boolean) входными данными, используемыми как атрибуты, например,<input disabled>. - При использовании
$event.targetдля событий DOM (из-за возможности всплытия событий,$event.targetв типизации DOM не имеет того типа, который вы могли бы ожидать).
В случае подобных ложных срабатываний есть несколько вариантов:
- Использовать функцию приведения типов
$any()в определенных контекстах, чтобы отказаться от проверки типов для части выражения. - Полностью отключить строгие проверки, установив
strictTemplates: falseв файле конфигурации TypeScript приложенияtsconfig.json. - Отключить отдельные операции проверки типов, сохраняя строгость в других аспектах, установив соответствующий флаг
строгости в
false. - Если вы хотите использовать
strictTemplatesиstrictNullChecksвместе, откажитесь от строгой проверки null специально для входных привязок, используяstrictNullInputTypes.
Если не указано иное, каждая следующая опция устанавливается в значение strictTemplates (true, когда
strictTemplates равно true, и наоборот).
| Флаг строгости | Эффект |
|---|---|
strictInputTypes |
Проверяется ли возможность присвоения выражения привязки пол�� @Input(). Также влияет на вывод типов дженериков директив. |
strictInputAccessModifiers |
Учитываются ли модификаторы доступа, такие как private/protected/readonly, при присвоении выражения привязки @Input() или input(). Если отключено, модификаторы доступа входного свойства игнорируются; проверяется только тип. Эта опция по умолчанию false, даже если strictTemplates установлено в true. Примечание: Эта проверка применяется только к входным свойствам, не к output. |
strictNullInputTypes |
Учитывается ли strictNullChecks при проверке привязок @Input() (согласно strictInputTypes). Отключение этой опции может быть полезно при использовании библиотеки, которая не была создана с учетом strictNullChecks. |
strictAttributeTypes |
Проверять ли привязки @Input(), сделанные с использованием текстовых атрибутов. Например, <input matInput disabled="true"> (установка свойства disabled в строку 'true') против <input matInput [disabled]="true"> (установка свойства disabled в логическое значение true). |
strictSafeNavigationTypes |
Будет ли корректно выводиться возвращаемый тип операций безопасной навигации (например, тип user?.name будет выведен на основе типа user). Если отключено, user?.name будет иметь тип any. |
strictDomLocalRefTypes |
Будут ли локальные ссылки на DOM-элементы иметь правильный тип. Если отключено, ref будет иметь тип any для <input #ref>. |
strictOutputEventTypes |
Будет ли $event иметь правильный тип для привязок событий к @Output() компонента/директивы или к событиям анимации. Если отключено, он будет any. |
strictDomEventTypes |
Будет ли $event иметь правильный тип для привязок к событиям DOM. Если отключено, он будет any. |
strictContextGenerics |
Будут ли параметры типа обобщенных (generic) компонентов выводиться корректно (включая любые ограничения дженериков). Если отключено, любые параметры типа будут any. |
strictLiteralTypes |
Будет ли выводиться тип объектных литералов и массивов, объявленных в шаблоне. Если отключено, тип таких литералов будет any. Этот флаг равен true, когда либо fullTemplateTypeCheck, либо strictTemplates установлены в true. |
Если после устранения неполадок с помощью этих флагов проблемы сохраняются, вернитесь к полному режиму, отключив
strictTemplates.
Если это не поможет, крайний вариант — полностью отключить полный режим с помощью fullTemplateTypeCheck: false.
Ошибка проверки типов, которую вы не можете устранить ни одним из рекомендуемых методов, может быть результатом ошибки в самом средстве проверки типов шаблонов. Если вы получаете ошибки, требующие возврата к базовому режиму, вероятно, это именно такая ошибка. В этом случае создайте issue, чтобы команда могла её исправить.
Input-ы и проверка типов
Средство проверки типов шаблонов проверяет, совместим ли тип выражения привязки с типом соответствующего входного свойства директивы. В качестве примера рассмотрим следующий компонент:
export interface User {
name: string;
}
@Component({
selector: 'user-detail',
template: '{{ user.name }}',
})
export class UserDetailComponent {
user = input.required<User>();
}
Шаблон AppComponent использует этот компонент следующим образом:
@Component({
selector: 'app-root',
template: '<user-detail [user]="selectedUser"></user-detail>',
})
export class AppComponent {
selectedUser: User | null = null;
}
Здесь, во время проверки типов шаблона для AppComponent, привязка [user]="selectedUser" соответствует входному
свойству UserDetailComponent.user.
Следовательно, Angular присваивает свойство selectedUser свойству UserDetailComponent.user, что привело бы к ошибке,
если бы их типы были несовместимы.
TypeScript проверяет присваивание в соответствии со своей системой типов, соблюдая такие флаги, как strictNullChecks,
так, как они настроены в приложении.
Избегайте ошибок типов во время выполнения, предоста��ляя средству проверки типов шаблонов более конкретные требования к типам внутри шаблона. Сделайте требования к типам входных данных для ваших собственных директив максимально конкретными, предоставив функции защиты шаблона (template-guard) в определении директивы. См. Улучшение проверки типов в шаблонах для пользовательских директив в этом руководстве.
Строгие проверки null (Strict null checks)
При включении strictTemplates и флага TypeScript strictNullChecks могут возникать ошибки проверки типов в
определенных ситуациях, которых нелегко избежать.
Например:
Значение, допускающее null (nullable), привязано к директиве из библиотеки, в которой не был включен
strictNullChecks.Для библиотеки, скомпилированной без
strictNullChecks, файлы деклараций не будут указывать, может ли поле бытьnullили нет. Для ситуаций, когда библиотека корректно обрабатываетnull, это проблематично, так как компилятор будет проверять nullable-значение на соответствие файлам деклараций, в которых отсутствует типnull. В результате компилятор выдает ошибку проверки типа, поскольку он придерживаетсяstrictNullChecks.Использование Pipe
asyncс Observable, который, как вы знаете, будет выдавать значение синхронно.Pipe
asyncв настоящее время предполагает, что Observable, на который он подписывается, может быть асинхронным, что означает возможность отсутствия доступного значения в данный момент. В этом случае он все равно должен что-то вернуть — и этоnull. Другими словами, возвращаемый тип Pipeasyncвключаетnull, что может привести к ошибкам в ситуациях, когда известно, что Observable синхронно выдает значение, отличное от null.
Существует два возможных обходных пути для вышеуказанных проблем:
- В шаблоне включите оператор утверждения non-null
!в конце выражения, допускающего null, например:
<user-detail [user]="user!"></user-detail>
В этом примере компилятор игнорирует несовместимость типов в отношении nullability, так же как и в коде TypeScript.
В случае с Pipe async обратите внимание, что выражение нужно заключить в скобки, как здесь:
<user-detail [user]="(user$ | async)!"></user-detail>
Полностью отключить строгие проверки null в шаблонах Angular.
Когда
strictTemplatesвключен, все еще можно отключить определенные ��спекты проверки типов. Установка опцииstrictNullInputTypesвfalseотключает строгие проверки null внутри шаблонов Angular. Этот флаг применяется ко всем компонентам, являющимся частью приложения.
Совет для авторов библиотек
Как автор библиотеки, вы можете принять несколько мер для обеспечения оптимального опыта для ваших пользователей.
Во-первых, включение strictNullChecks и включение null в тип входного свойства, где это уместно, сообщает вашим
потребителям, могут ли они предоставить значение null или нет.
Кроме того, можно предоставить подсказки типов, специфичные для средства проверки типов шаблонов.
См. Улучшение проверки типов в шаблонах для пользовательских директив
и Приведение типов в сеттерах Input.
Приведение типов в сеттерах Input
Иногда желательно, чтобы свойство input() директивы или компонента изменяло привязанное к нему значение, обычно
используя функцию transform для входного свойства.
В качестве примера рассмотрим этот пользовательский компонент кнопки:
Рассмотрим следующую директиву:
@Component({
selector: 'submit-button',
template: `
<div class="wrapper">
<button [disabled]="disabled">Submit</button>
</div>
`,
})
class SubmitButton {
disabled = input.required({transform: booleanAttribute});
}
Здесь входное свойство disabled компонента передается в <button> в шаблоне.
Все это работает как ожидается, пока к входному свойству привязано значение boolean.
Но предположим, что потребитель использует это входное свойство в шаблоне как атрибут:
Это имеет тот же эффект, что и привязка:
Во время выполнения входное свойство будет установлено в пустую строку, которая не является значением boolean.
Библиотеки компонентов Angular, сталкивающиеся с этой проблемой, часто «приводят» (coerce) значение к правильному типу в
сеттере:
set disabled(value: boolean) {
this._disabled = (value === '') || value;
}
Было бы идеально изменить здесь тип value с boolean на boolean|'', чтобы он соответствовал набору значений,
которые фактически принимаются сеттером.
TypeScript до версии 4.3 требовал, чтобы геттер и сеттер имели одинаковый тип, поэтому, если геттер должен возвращать
boolean, то сеттер вынужден использовать более узкий тип.
Если у потребителя включена самая строгая проверка типов шаблонов Angular, это создает проблему: пустая строка ('')
фактически не может быть присвоена полю disabled, что вызывает ошибку типа при использовании формы атрибута.
В качестве обходного пути для этой проблемы Angular поддерживает проверку более широкого, более разрешительного типа для
@Input(), чем объявлено для самого поля ввода.
Включите это, добавив статическое свойство с префиксом ngAcceptInputType_ в класс компонента:
class SubmitButton {
private _disabled: boolean;
@Input()
get disabled(): boolean {
return this._disabled;
}
set disabled(value: boolean) {
this._disabled = value === '' || value;
}
static ngAcceptInputType_disabled: boolean | '';
}
Начиная с TypeScript 4.3, сеттер можно объявить принимающим тип boolean|'', что делает поле приведения типа сеттера
устаревшим.
В связи с этим поля приведения типов сеттеров входных свойств устарели.
Это поле не обязательно должно иметь значение.
Его существование сообщает средству проверки типов Angular, что входное свойство disabled следует рассматривать как
принимающее привязки, соответствующие типу boolean|''.
Суффикс должен совпадать с именем поля @Input.
Следует позаботиться о том, что если для данного входного свойства присутствует переопределение ngAcceptInputType_, то
сеттер должен уметь обрабатывать любые значения переопределенного типа.
Отключение проверки типов с помощью $any()
Отключите проверку выражения привязки, заключив выражение в вызов псевдо-функции приведения типов $any().
Компилятор рассматривает это как приведение к типу any, точно так же, как в TypeScript при использовании приведения
<any> или as any.
В следующем примере приведение person к типу any подавляет ошибку Property address does not exist.
@Component({
selector: 'my-component',
template: '{{$any(person).address.street}}',
})
class MyComponent {
person?: Person;
}