Visión general de la verificación de tipos de plantilla
Así como TypeScript captura errores de tipo en tu código, Angular verifica las expresiones y enlaces dentro de las plantillas de tu aplicación y puede reportar cualquier error de tipo que encuentre.
Angular actualmente tiene tres modos de hacer esto, dependiendo del valor de las banderas fullTemplateTypeCheck y strictTemplates en las opciones del compilador de Angular.
Modo básico
En el modo de verificación de tipos más básico, con la bandera fullTemplateTypeCheck establecida en false, Angular valida solo expresiones de nivel superior en una plantilla.
Si escribes <map [city]="user.address.city">, el compilador verifica lo siguiente:
useres una propiedad en la clase del componenteuseres un objeto con una propiedad addressuser.addresses un objeto con una propiedad city
El compilador no verifica que el valor de user.address.city sea asignable a la entrada city del componente <map>.
El compilador también tiene algunas limitaciones importantes en este modo:
- Importantemente, no verifica vistas incrustadas, como
*ngIf,*ngFor, u otras vistas incrustadas<ng-template>. - No determina los tipos de
#refs, los resultados de pipes, o el tipo de$eventen enlaces de eventos.
En muchos casos, estas cosas terminan siendo de tipo any, lo que puede causar que partes subsiguientes de la expresión no se verifiquen.
Modo completo
Si la bandera fullTemplateTypeCheck está establecida en true, Angular es más agresivo en su verificación de tipos dentro de plantillas.
En particular:
- Las vistas incrustadas (como aquellas dentro de un
*ngIfo*ngFor) son verificadas - Los pipes tienen el tipo de retorno correcto
- Las referencias locales a directivas y pipes tienen el tipo correcto (excepto por cualquier parámetro genérico, que será
any)
Lo siguiente todavía tiene tipo any.
- Referencias locales a elementos DOM
- El objeto
$event - Expresiones de navegación segura
IMPORTANTE: La bandera fullTemplateTypeCheck ha sido deprecada en Angular 13.
La familia de opciones de compilador strictTemplates debería usarse en su lugar.
Modo estricto
Angular mantiene el comportamiento de la bandera fullTemplateTypeCheck, e introduce un tercer "modo estricto".
El modo estricto es un superconjunto del modo completo, y se accede estableciendo la bandera strictTemplates en true.
Esta bandera reemplaza la bandera fullTemplateTypeCheck.
Además del comportamiento del modo completo, Angular hace lo siguiente:
- Verifica que los enlaces de componente/directiva sean asignables a sus
input()s - Obedece la bandera
strictNullChecksde TypeScript al validar el modo precedente - Infiere el tipo correcto de componentes/directivas, incluyendo genéricos
- Infiere tipos de contexto de plantilla donde está configurado (por ejemplo, permitiendo verificación de tipos correcta de
NgFor) - Infiere el tipo correcto de
$eventen enlaces de eventos de componente/directiva, DOM y animación - Infiere el tipo correcto de referencias locales a elementos DOM, basado en el nombre de etiqueta (por ejemplo, el tipo que
document.createElementdevolvería para esa etiqueta)
Verificación de *ngFor
Los tres modos de verificación de tipos tratan las vistas incrustadas de manera diferente. Considera el siguiente ejemplo.
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>
El <h2> y el <span> están en la vista incrustada *ngFor.
En modo básico, Angular no verifica ninguno de ellos.
Sin embargo, en modo completo, Angular verifica que config y user existan y asume un tipo de any.
En modo estricto, Angular sabe que el user en el <span> tiene un tipo de User, y que address es un objeto con una propiedad city de tipo string.
Solución de problemas de errores de plantilla
Con el modo estricto, podrías encontrar errores de plantilla que no surgieron en ninguno de los modos anteriores. Estos errores a menudo representan desajustes de tipo genuinos en las plantillas que no fueron capturados por las herramientas anteriores. Si este es el caso, el mensaje de error debería dejar claro dónde en la plantilla ocurre el problema.
También puede haber falsos positivos cuando los tipos de una librería Angular son incompletos o incorrectos, o cuando los tipos no se alinean del todo con las expectativas como en los siguientes casos.
Cuando los tipos de una librería son incorrectos o incompletos (por ejemplo, falta
null | undefinedsi la librería no fue escrita constrictNullChecksen mente)Cuando los tipos de entrada de una librería son demasiado estrechos y la librería no ha agregado metadata apropiada para que Angular lo determine. Esto usualmente ocurre con disabled u otras entradas Boolean comunes usadas como atributos, por ejemplo,
<input disabled>.Cuando se usa
$event.targetpara eventos DOM (debido a la posibilidad de propagación de eventos,$event.targeten los tipos DOM no tiene el tipo que podrías esperar)
En caso de un falso positivo como estos, hay algunas opciones:
- Usa la función de conversión de tipo
$any()en ciertos contextos para optar por no verificar tipos para una parte de la expresión - Deshabilita las verificaciones estrictas completamente estableciendo
strictTemplates: falseen el archivo de configuración TypeScript de la aplicación,tsconfig.json - Deshabilita ciertas operaciones de verificación de tipos individualmente, mientras mantienes rigurosidad en otros aspectos, estableciendo una bandera de rigurosidad en
false - Si quieres usar
strictTemplatesystrictNullChecksjuntos, opta por no verificar tipos null estrictamente específicamente para enlaces de entrada usandostrictNullInputTypes
A menos que se comente lo contrario, cada opción siguiente se establece al valor de strictTemplates (true cuando strictTemplates es true y viceversa, de lo contrario).
| Bandera de rigurosidad | Efecto |
|---|---|
strictInputTypes |
Si se verifica la asignabilidad de una expresión de enlace al campo @Input(). También afecta la inferencia de tipos genéricos de directiva. |
strictInputAccessModifiers |
Si se respetan los modificadores de acceso como private/protected/readonly al asignar una expresión de enlace a un @Input(). Si se deshabilita, los modificadores de acceso del @Input se ignoran; solo se verifica el tipo. Esta opción es false por defecto, incluso con strictTemplates establecido en true. |
strictNullInputTypes |
Si se respeta strictNullChecks al verificar enlaces @Input() (según strictInputTypes). Desactivar esto puede ser útil cuando se usa una librería que no fue construida con strictNullChecks en mente. |
strictAttributeTypes |
Si se deben verificar enlaces @Input() que se hacen usando atributos de texto. Por ejemplo, <input matInput disabled="true"> (estableciendo la propiedad disabled a la cadena 'true') vs <input matInput [disabled]="true"> (estableciendo la propiedad disabled al booleano true). |
strictSafeNavigationTypes |
Si el tipo de retorno de operaciones de navegación segura (por ejemplo, user?.name se inferirá correctamente basado en el tipo de user). Si se deshabilita, user?.name será de tipo any. |
strictDomLocalRefTypes |
Si las referencias locales a elementos DOM tendrán el tipo correcto. Si se deshabilita ref será de tipo any para <input #ref>. |
strictOutputEventTypes |
Si $event tendrá el tipo correcto para enlaces de eventos a @Output() de componente/directiva, o a eventos de animación. Si se deshabilita, será any. |
strictDomEventTypes |
Si $event tendrá el tipo correcto para enlaces de eventos a eventos DOM. Si se deshabilita, será any. |
strictContextGenerics |
Si los parámetros de tipo de componentes genéricos se inferirán correctamente (incluyendo cualquier límite genérico). Si se deshabilita, cualquier parámetro de tipo será any. |
strictLiteralTypes |
Si los literales de objeto y array declarados en la plantilla tendrán su tipo inferido. Si se deshabilita, el tipo de tales literales será any. Esta bandera es true cuando cualquiera de fullTemplateTypeCheck o strictTemplates está establecido en true. |
Si aún tienes problemas después de solucionar problemas con estas banderas, vuelve al modo completo deshabilitando strictTemplates.
Si eso no funciona, una opción de último recurso es desactivar el modo completo completamente con fullTemplateTypeCheck: false.
Un error de verificación de tipos que no puedes resolver con ninguno de los métodos recomendados puede ser el resultado de un bug en el verificador de tipos de plantilla mismo. Si obtienes errores que requieren volver al modo básico, es probable que sea tal bug. Si esto sucede, abre un issue para que el equipo pueda abordarlo.
Entradas y verificación de tipos
El verificador de tipos de plantilla verifica si el tipo de una expresión de enlace es compatible con el de la entrada de directiva correspondiente. Como ejemplo, considera el siguiente componente:
export interface User { name: string;}@Component({ selector: 'user-detail', template: '{{ user.name }}',})export class UserDetailComponent { user = input.required<User>();}
La plantilla de AppComponent usa este componente como sigue:
@Component({ selector: 'app-root', template: '<user-detail [user]="selectedUser"></user-detail>',})export class AppComponent { selectedUser: User | null = null;}
Aquí, durante la verificación de tipos de la plantilla para AppComponent, el enlace [user]="selectedUser" corresponde con la entrada UserDetailComponent.user.
Por lo tanto, Angular asigna la propiedad selectedUser a UserDetailComponent.user, lo que resultaría en un error si sus tipos fueran incompatibles.
TypeScript verifica la asignación según su sistema de tipos, obedeciendo banderas como strictNullChecks como están configuradas en la aplicación.
Evita errores de tipo en tiempo de ejecución proporcionando requisitos de tipo más específicos en la plantilla al verificador de tipos de plantilla. Haz que los requisitos de tipo de entrada para tus propias directivas sean lo más específicos posible proporcionando funciones de guardia de plantilla en la definición de la directiva. Consulta Mejorando la verificación de tipos de plantilla para directivas personalizadas en esta guía.
Verificaciones null estrictas
Cuando habilitas strictTemplates y la bandera TypeScript strictNullChecks, pueden ocurrir errores de verificación de tipos para ciertas situaciones que podrían no evitarse fácilmente.
Por ejemplo:
Un valor nullable que está enlazado a una directiva de una librería que no tenía
strictNullCheckshabilitado.Para una librería compilada sin
strictNullChecks, sus archivos de declaración no indicarán si un campo puede sernullo no. Para situaciones donde la librería manejanullcorrectamente, esto es problemático, ya que el compilador verificará un valor nullable contra los archivos de declaración que omiten el tiponull. Como tal, el compilador produce un error de verificación de tipos porque se adhiere astrictNullChecks.Usar el pipe
asynccon un Observable que sabes que emitirá sincrónicamente.El pipe
asyncactualmente asume que el Observable al que se suscribe puede ser asíncrono, lo que significa que es posible que aún no haya un valor disponible. En ese caso, aún tiene que devolver algo —que esnull. En otras palabras, el tipo de retorno del pipeasyncincluyenull, lo que podría resultar en errores en situaciones donde se sabe que el Observable emite un valor no nullable sincrónicamente.
Hay dos soluciones potenciales para los problemas anteriores:
En la plantilla, incluye el operador de aserción no null
!al final de una expresión nullable, como<user-detail [user]="user!"></user-detail>En este ejemplo, el compilador ignora incompatibilidades de tipo en nulabilidad, al igual que en código TypeScript. En el caso del pipe
async, nota que la expresión necesita estar envuelta en paréntesis, como en<user-detail [user]="(user$ | async)!"></user-detail>Deshabilita verificaciones null estrictas en plantillas Angular completamente.
Cuando
strictTemplatesestá habilitado, aún es posible deshabilitar ciertos aspectos de la verificación de tipos. Establecer la opciónstrictNullInputTypesenfalsedeshabilita verificaciones null estrictas dentro de plantillas Angular. Esta bandera se aplica para todos los componentes que son parte de la aplicación.
Consejo para autores de librerías
Como autor de librería, puedes tomar varias medidas para proporcionar una experiencia óptima para tus usuarios.
Primero, habilitar strictNullChecks e incluir null en el tipo de una entrada, según corresponda, comunica a tus consumidores si pueden proporcionar un valor nullable o no.
Adicionalmente, es posible proporcionar pistas de tipo específicas para el verificador de tipos de plantilla.
Consulta Mejorando la verificación de tipos de plantilla para directivas personalizadas, y Coerción de setter de entrada.
Coerción de setter de entrada
Ocasionalmente es deseable que la propiedad input() de una directiva o componente altere el valor enlazado a ella, típicamente usando una función transform para la entrada.
Como ejemplo, considera este componente de botón personalizado:
Considera la siguiente directiva:
@Component({ selector: 'submit-button', template: ` <div class="wrapper"> <button [disabled]="disabled">Submit</button> </div> `,})class SubmitButton { disabled = input.required({transform: booleanAttribute });}
Aquí, la entrada disabled del componente se pasa al <button> en la plantilla.
Todo esto funciona como se espera, siempre que un valor boolean esté enlazado a la entrada.
Pero, supón que un consumidor usa esta entrada en la plantilla como un atributo:
<submit-button disabled></submit-button>
Esto tiene el mismo efecto que el enlace:
<submit-button [disabled]="''"></submit-button>
En tiempo de ejecución, la entrada se establecerá a la cadena vacía, que no es un valor boolean.
Las librerías de componentes Angular que lidian con este problema a menudo "coercionan" el valor al tipo correcto en el setter:
set disabled(value: boolean) { this._disabled = (value === '') || value;}
Sería ideal cambiar el tipo de value aquí, de boolean a boolean|'', para que coincida con el conjunto de valores que realmente son aceptados por el setter.
TypeScript anterior a la versión 4.3 requiere que tanto el getter como el setter tengan el mismo tipo, por lo que si el getter debe devolver un boolean entonces el setter está atascado con el tipo más estrecho.
Si el consumidor tiene habilitada la verificación de tipos más estricta de Angular para plantillas, esto crea un problema: la cadena vacía ('') no es realmente asignable al campo disabled, lo que crea un error de tipo cuando se usa la forma de atributo.
Como solución a este problema, Angular soporta verificar un tipo más amplio y permisivo para @Input() que el declarado para el campo de entrada mismo.
Habilita esto agregando una propiedad estática con el prefijo ngAcceptInputType_ a la clase del componente:
class SubmitButton { private _disabled: boolean; @Input() get disabled(): boolean { return this._disabled; } set disabled(value: boolean) { this._disabled = (value === '') || value; } static ngAcceptInputType_disabled: boolean|'';}
Desde TypeScript 4.3, el setter podría haberse declarado para aceptar boolean|'' como tipo, haciendo obsoleto el campo de coerción de setter de entrada.
Como tal, los campos de coerción de setters de entrada han sido deprecados.
Este campo no necesita tener un valor.
Su existencia comunica al verificador de tipos de Angular que la entrada disabled debería considerarse como aceptando enlaces que coincidan con el tipo boolean|''.
El sufijo debe ser el nombre del campo @Input.
Se debe tener cuidado de que si un override ngAcceptInputType_ está presente para una entrada dada, entonces el setter debería poder manejar cualquier valor del tipo sobrescrito.
Deshabilitando verificación de tipos usando $any()
Deshabilita la verificación de una expresión de enlace rodeando la expresión en una llamada a la pseudo-función de conversión $any().
El compilador la trata como una conversión al tipo any al igual que en TypeScript cuando se usa una conversión <any> o as any.
En el siguiente ejemplo, convertir person al tipo any suprime el error Property address does not exist.
@Component({ selector: 'my-component', template: '{{$any(person).address.street}}'})class MyComponent { person?: Person;}