Présentation de la vérification de types des templates
Tout comme TypeScript détecte les erreurs de type dans votre code, Angular vérifie les expressions et les bindings dans les templates de votre application et peut signaler toutes les erreurs de type qu'il trouve.
Par défaut, Angular vérifie les templates en mode strict.
Si nécessaire, vous pouvez le désactiver temporairement en définissant l'option strictTemplates à false dans les options du compilateur Angular, ce qui revient à un mode de base moins exhaustif.
Mode de base
Dans le mode de vérification de types de base, avec l'option strictTemplates définie à false, Angular ne valide que les expressions de premier niveau d'un template.
Si vous écrivez <map [city]="user.address.city">, le compilateur vérifie les points suivants :
userest une propriété de la classe du composantuserest un objet doté d'une propriété addressuser.addressest un objet doté d'une propriété city
Le compilateur ne vérifie pas que la valeur de user.address.city est assignable à l'input city du composant <map>.
Le compilateur présente aussi d'importantes limitations dans ce mode :
- Surtout, il ne vérifie pas les vues intégrées, comme
@if,@for,*ngIf,*ngForou les autres vues intégrées<ng-template>. - Il ne détermine pas les types des
#refs, les résultats des pipes, ni le type de$eventdans les event bindings.
Dans de nombreux cas, ces éléments finissent avec le type any, ce qui peut empêcher la vérification des parties suivantes de l'expression.
Mode strict
Le mode strict est le mode par défaut ; il est activé lorsque l'option strictTemplates vaut true ou n'est pas définie.
En mode strict, Angular effectue les opérations suivantes :
- Vérifie les vues intégrées (comme celles de
@if,@for,*ngIfou*ngFor) - Infère le type de retour correct des pipes
- Infère le type correct des références locales vers des directives et des pipes
- Vérifie que les bindings de composant ou de directive sont assignables à leurs
input() - Respecte l'option
strictNullChecksde TypeScript lors de la validation des bindings d'inputs - Infère le type correct des composants et des directives, y compris les génériques
- Infère les types de contexte des templates lorsqu'ils sont configurés (par exemple, pour permettre une vérification de types correcte de
NgFor) - Infère le type correct de
$eventdans les event bindings de composant ou de directive, du DOM et des animations - Infère le type correct des références locales vers des éléments du DOM, en fonction du nom de la balise (par exemple, le type que
document.createElementrenverrait pour cette balise)
Vérification de *ngFor
Les deux modes de vérification de types traitent les vues intégrées différemment. Considérez l'exemple suivant.
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>
Les éléments <h2> et <span> se trouvent dans la vue intégrée de *ngFor.
En mode de base, Angular ne vérifie aucun des deux.
En mode strict, Angular sait que user dans le <span> est de type User, et que address est un objet doté d'une propriété city de type string.
Résoudre les erreurs de template
En mode strict, vous pouvez rencontrer des erreurs de template qui n'apparaissaient pas en mode de base. Ces erreurs correspondent souvent à de véritables incompatibilités de types dans les templates, que l'outillage précédent ne détectait pas. Dans ce cas, le message d'erreur devrait indiquer clairement à quel endroit du template le problème se produit.
Il peut aussi y avoir des faux positifs lorsque les typings d'une library Angular sont incomplets ou incorrects, ou lorsque les typings ne correspondent pas tout à fait aux attentes, comme dans les cas suivants.
Lorsque les typings d'une library sont faux ou incomplets (par exemple, s'il manque
null | undefinedparce que la library n'a pas été écrite en tenant compte destrictNullChecks)Lorsque les types des inputs d'une library sont trop restrictifs et que la library n'a pas ajouté les métadonnées appropriées pour qu'Angular le comprenne. Cela se produit généralement avec disabled ou d'autres inputs booléens courants utilisés comme attributs, par exemple
<input disabled>.Lorsque vous utilisez
$event.targetpour les événements du DOM (en raison de la propagation possible des événements,$event.targetn'a pas, dans les typings du DOM, le type auquel vous pourriez vous attendre)
En cas de faux positif de ce type, plusieurs options s'offrent à vous :
- Utilisez la fonction de conversion de type
$any()dans certains contextes pour désactiver la vérification de types d'une partie de l'expression - Désactivez entièrement les vérifications strictes en définissant
strictTemplates: falsedans le fichier de configuration TypeScript de l'application,tsconfig.json - Désactivez individuellement certaines opérations de vérification de types, tout en conservant la rigueur sur les autres aspects, en définissant une option de rigueur à
false - Si vous voulez utiliser
strictTemplatesetstrictNullChecksensemble, désactivez la vérification stricte des types null spécifiquement pour les bindings d'inputs avecstrictNullInputTypes
Chacune des options suivantes prend la valeur de strictTemplates (true lorsque strictTemplates vaut true, et inversement) :
| Option de rigueur | Effet |
|---|---|
strictInputTypes |
Indique si l'assignabilité d'une expression de binding au champ @Input() est vérifiée. Affecte aussi l'inférence des types génériques des directives. |
strictNullInputTypes |
Indique si strictNullChecks est respecté lors de la vérification des bindings @Input() (selon strictInputTypes). Le désactiver peut être utile lorsque vous utilisez une library qui n'a pas été compilée en tenant compte de strictNullChecks. |
strictAttributeTypes |
Indique s'il faut vérifier les bindings @Input() réalisés avec des attributs texte. Par exemple, <input matInput disabled="true"> (qui définit la propriété disabled à la chaîne 'true') par rapport à <input matInput [disabled]="true"> (qui définit la propriété disabled au booléen true). |
strictSafeNavigationTypes |
Indique si le type de retour des opérations de navigation sécurisée est inféré (par exemple, user?.name sera correctement inféré en fonction du type de user). Si l'option est désactivée, user?.name sera de type any. |
strictDomLocalRefTypes |
Indique si les références locales vers des éléments du DOM auront le type correct. Si l'option est désactivée, ref sera de type any pour <input #ref>. |
strictOutputEventTypes |
Indique si $event aura le type correct pour les event bindings vers un @Output() de composant ou de directive, ou vers des événements d'animation. Si l'option est désactivée, il sera de type any. |
strictDomEventTypes |
Indique si $event aura le type correct pour les event bindings vers des événements du DOM. Si l'option est désactivée, il sera de type any. |
strictContextGenerics |
Indique si les paramètres de type des composants génériques seront inférés correctement (y compris les contraintes génériques). Si l'option est désactivée, tous les paramètres de type seront any. |
strictLiteralTypes |
Indique si le type des objets et tableaux littéraux déclarés dans le template sera inféré. Si l'option est désactivée, le type de ces littéraux sera any. |
Autres options (indépendantes de strictTemplates) :
| Option de rigueur | Effet |
|---|---|
strictInputAccessModifiers |
Indique si les modificateurs d'accès comme private/protected/readonly sont respectés lors de l'assignation d'une expression de binding à un @Input() ou à un input(). Si l'option est désactivée, les modificateurs d'accès de l'input sont ignorés ; seul le type est vérifié. Remarque : cette vérification ne s'applique qu'aux inputs, pas aux outputs. |
strictUnclaimedEventNames |
Indique s'il faut signaler les event bindings dont le nom ne correspond ni à un output d'une directive appliquée à l'élément, ni à un événement natif connu du DOM (ceux décrits par la GlobalEventHandlersEventMap de TypeScript, comparés sans tenir compte de la casse), ce qui indique généralement un nom d'output mal orthographié. Seuls les noms en camelCase composés d'un seul identifiant sont vérifiés ; les noms séparés par des tirets (par exemple, my-event), utilisés par convention pour les événements personnalisés émis par les descendants, sont exemptés, de même que les éléments sans composant correspondant lorsque CUSTOM_ELEMENTS_SCHEMA est utilisé. Cette option vaut false par défaut, même avec strictTemplates défini à true. |
Si vous rencontrez encore des problèmes après avoir essayé ces options, le dernier recours consiste à revenir au mode de base en désactivant strictTemplates.
Une erreur de vérification de types que vous ne parvenez à résoudre avec aucune des méthodes recommandées peut provenir d'un bug dans le vérificateur de types des templates lui-même. Si vous obtenez des erreurs qui vous obligent à revenir au mode de base, il s'agit probablement d'un tel bug. Dans ce cas, ouvrez une issue pour que l'équipe puisse le corriger.
Inputs et vérification de types
Le vérificateur de types des templates vérifie si le type d'une expression de binding est compatible avec celui de l'input correspondant de la directive. Par exemple, considérez le composant suivant :
export interface User {
name: string;
}
@Component({
selector: 'user-detail',
template: '{{ user().name }}',
})
export class UserDetailComponent {
user = input.required<User>();
}
Le template de AppComponent utilise ce composant comme suit :
@Component({
selector: 'app-root',
template: '<user-detail [user]="selectedUser"></user-detail>',
})
export class AppComponent {
selectedUser: User | null = null;
}
Ici, pendant la vérification de types du template de AppComponent, le binding [user]="selectedUser" correspond à l'input UserDetailComponent.user.
Angular assigne donc la propriété selectedUser à UserDetailComponent.user, ce qui produirait une erreur si leurs types étaient incompatibles.
TypeScript vérifie l'assignation selon son système de types, en respectant les options comme strictNullChecks telles qu'elles sont configurées dans l'application.
Évitez les erreurs de type à l'exécution en fournissant au vérificateur de types des templates des exigences de type plus précises dans le template. Rendez les exigences de type des inputs de vos propres directives aussi précises que possible en fournissant des fonctions de template guard dans la définition de la directive. Consultez Améliorer la vérification de types des templates pour les directives personnalisées dans ce guide.
Vérifications strictes des valeurs null
Lorsque vous activez strictTemplates et l'option TypeScript strictNullChecks, des erreurs de vérification de types peuvent survenir dans certaines situations difficiles à éviter.
Par exemple :
Une valeur nullable liée à une directive provenant d'une library qui n'avait pas activé
strictNullChecks.Pour une library compilée sans
strictNullChecks, ses fichiers de déclaration n'indiquent pas si un champ peut êtrenullou non. Lorsque la library gère correctementnull, c'est problématique, car le compilateur vérifie une valeur nullable par rapport à des fichiers de déclaration qui omettent le typenull. Le compilateur produit donc une erreur de vérification de types, car il respectestrictNullChecks.L'utilisation du pipe
asyncavec un observable dont vous savez qu'il émettra de façon synchrone.Le pipe
asyncsuppose actuellement que l'observable auquel il s'abonne peut être asynchrone, ce qui signifie qu'il est possible qu'aucune valeur ne soit encore disponible. Dans ce cas, il doit tout de même renvoyer quelque chose, à savoirnull. Autrement dit, le type de retour du pipeasyncinclutnull, ce qui peut entraîner des erreurs dans les situations où l'on sait que l'observable émet de façon synchrone une valeur non nullable.
Il existe deux contournements possibles aux problèmes précédents :
- Dans le template, ajoutez l'opérateur d'assertion non-null
!à la fin d'une expression nullable, par exemple
<user-detail [user]="user!"></user-detail>
Dans cet exemple, le compilateur ignore les incompatibilités de types liées à la nullabilité, comme dans du code TypeScript.
Dans le cas du pipe async, notez que l'expression doit être placée entre parenthèses, par exemple
<user-detail [user]="(user$ | async)!"></user-detail>
Désactivez complètement les vérifications strictes des valeurs null dans les templates Angular.
Lorsque
strictTemplatesest activé, il reste possible de désactiver certains aspects de la vérification de types. Définir l'optionstrictNullInputTypesàfalsedésactive les vérifications strictes des valeurs null dans les templates Angular. Cette option s'applique à tous les composants qui font partie de l'application.
Conseils pour les auteurs de libraries
En tant qu'auteur de library, vous pouvez prendre plusieurs mesures pour offrir une expérience optimale à vos utilisateurs.
Tout d'abord, activer strictNullChecks et inclure null dans le type d'un input, lorsque c'est approprié, indique à vos utilisateurs s'ils peuvent fournir une valeur nullable ou non.
De plus, il est possible de fournir des indications de type propres au vérificateur de types des templates.
Consultez Améliorer la vérification de types des templates pour les directives personnalisées et Coercition dans le setter d'input.
Coercition dans le setter d'input
Il est parfois souhaitable que la propriété input() d'une directive ou d'un composant modifie la valeur qui lui est liée, généralement à l'aide d'une fonction transform pour l'input.
Par exemple, considérez ce composant de bouton personnalisé :
Considérez la directive suivante :
@Component({
selector: 'submit-button',
template: `
<div class="wrapper">
<button [disabled]="disabled()">Submit</button>
</div>
`,
})
class SubmitButton {
disabled = input.required({transform: booleanAttribute});
}
Ici, l'input disabled du composant est transmis au <button> dans le template.
Tout fonctionne comme prévu, tant qu'une valeur boolean est liée à l'input.
Mais supposons qu'un utilisateur utilise cet input dans le template comme un attribut :
Cela a le même effet que le binding suivant :
À l'exécution, l'input sera défini à la chaîne vide, qui n'est pas une valeur boolean.
Les libraries de composants Angular qui traitent ce problème « convertissent » souvent la valeur dans le bon type dans le setter :
set disabled(value: boolean) {
this._disabled = (value === '') || value;
}
L'idéal serait de changer ici le type de value, de boolean en boolean|'', pour correspondre à l'ensemble des valeurs réellement acceptées par le setter.
Avant la version 4.3, TypeScript exige que le getter et le setter aient le même type : si le getter doit renvoyer un boolean, le setter est donc contraint au type le plus restrictif.
Si l'utilisateur a activé la vérification de types la plus stricte d'Angular pour les templates, cela pose un problème : la chaîne vide ('') n'est en réalité pas assignable au champ disabled, ce qui produit une erreur de type lorsque la forme attribut est utilisée.
Pour contourner ce problème, Angular permet de vérifier un type plus large et plus permissif pour @Input() que celui déclaré pour le champ de l'input lui-même.
Activez ce comportement en ajoutant une propriété statique avec le préfixe ngAcceptInputType_ à la classe du composant :
class SubmitButton {
private _disabled: boolean;
@Input()
get disabled(): boolean {
return this._disabled;
}
set disabled(value: boolean) {
this._disabled = value === '' || value;
}
static ngAcceptInputType_disabled: boolean | '';
}
Depuis TypeScript 4.3, le setter peut être déclaré pour accepter le type boolean|'', ce qui rend obsolète le champ de coercition du setter d'input.
Les champs de coercition des setters d'input sont donc dépréciés.
Ce champ n'a pas besoin d'avoir de valeur.
Son existence indique au vérificateur de types d'Angular que l'input disabled doit être considéré comme acceptant les bindings correspondant au type boolean|''.
Le suffixe doit être le nom du champ @Input.
Veillez à ce que, si une surcharge ngAcceptInputType_ est présente pour un input donné, le setter puisse gérer toutes les valeurs du type surchargé.
Désactiver la vérification de types avec $any()
Désactivez la vérification d'une expression de binding en entourant l'expression d'un appel à la pseudo-fonction de conversion $any().
Le compilateur la traite comme une conversion vers le type any, comme en TypeScript lorsqu'une conversion <any> ou as any est utilisée.
Dans l'exemple suivant, la conversion de person vers le type any supprime l'erreur Property address does not exist.
@Component({
selector: 'my-component',
template: '{{$any(person).address.street}}',
})
class MyComponent {
person?: Person;
}