~4 min de lecture
TypeScript : Arrête d'utiliser any (et fais ça à la place)
Utiliser any en TypeScript, c'est comme mettre une ceinture de sécurité sans l'attacher. Tu as l'impression d'être
protégé, mais au premier choc, tu comprends que ça servait à rien.
Quand tu écris any, tu dis explicitement à TypeScript : "T'inquiète, je gère." Spoiler : tu ne gères pas. Personne ne
gère.
1. Ce que tu perds avec any
Zéro autocomplétion
Avec any, ton IDE devient muet. Pas de suggestions, pas de propriétés proposées. Tu codes à l'aveugle.
function processUser(user: any) {
// Ton IDE ne te propose rien ici
console.log(user.???);
}
Des erreurs invisibles
Le vrai danger : les typos passent complètement inaperçues.
type User = {
readonly id: number;
readonly name: string;
readonly email: string;
};
function displayUser(user: any) {
// Typo sur "email" → aucune erreur, aucun warning
console.log(user.emial);
}
TypeScript ne bronche pas. ESLint non plus. Tu découvres le bug en production quand un utilisateur te signale que son email n'apparaît pas.
Le problème en équipe
Quand tu bosses seul sur deux fichiers, ça passe encore. Sur un projet conséquent avec une équipe, c'est ingérable.
Personne n'est dans ta tête, personne ne va relire chaque ligne pour vérifier que user.adress aurait dû être
user.address.
2. Démonstration : appel API avec JSON Placeholder
Prenons un cas concret. Tu appelles une API qui retourne des utilisateurs :
// ❌ Ce qu'on voit trop souvent
@Injectable()
export class HttpUserAdapter {
private readonly _http = inject(HttpClient);
getUsers(): Observable<any> {
return this._http.get<any>('https://jsonplaceholder.typicode.com/users');
}
}
Dans ton composant :
@Component({
template: `
@for (user of users(); track user.id) {
<p>{{ user.name }} - {{ user.emial }}</p> <!-- Typo ! -->
}
`
})
export class UserList {
private readonly _userAdapter = inject(HttpUserAdapter);
protected readonly users = toSignal(this._userAdapter.getUsers(), {initialValue: []});
}
Résultat : aucune erreur à la compilation, aucun warning dans la console. Juste un affichage cassé que tu découvriras peut-être. Ou pas.
3. La fausse bonne idée : any[]
Premier réflexe de beaucoup de devs :
export class HttpUserAdapter {
private readonly _http = inject(HttpClient);
getUsers(): Observable<any[]> {
return this._http.get<any[]>('https://jsonplaceholder.typicode.com/users');
}
}
Tu sais maintenant que c'est une liste. Bravo. Mais chaque élément reste un any. Tu n'as toujours aucune protection
sur les propriétés.
4. La vraie solution : unknown
unknown, c'est "je ne sais pas ce que c'est, et je vais devoir le vérifier".
export class HttpUserAdapter {
private readonly _http = inject(HttpClient);
getUsers(): Observable<unknown[]> {
return this.#http.get<unknown[]>('https://jsonplaceholder.typicode.com/users');
}
}
Maintenant, si tu essaies d'utiliser directement les données :
// ❌ TypeScript refuse
const users = await firstValueFrom(this._userAdapter.getUsers());
console.log(users[0].name); // Error: 'unknown' is not assignable to type...
TypeScript te force à vérifier le type avant de l'utiliser. C'est exactement ce qu'on veut.
5. Créer un Type Guard
Pour transformer un unknown en type connu, tu crées un type guard :
type User = {
readonly id: number;
readonly name: string;
readonly email: string;
};
function isUser(value: unknown): value is User {
return (
value !== null &&
typeof value === 'object' &&
'id' in value &&
'name' in value &&
'email' in value &&
typeof (value as User).id === 'number' &&
typeof (value as User).name === 'string' &&
typeof (value as User).email === 'string'
);
}
Pourquoi
value !== null && typeof value === 'object'? Parce quetypeof nullretourne'object'en JavaScript. Un classique.
Utilisation :
export class HttpUserAdapter {
getUsers(): Observable<User[]> {
return this._http.get<unknown[]>('https://jsonplaceholder.typicode.com/users').pipe(
map(results => results.filter(isUser))
);
}
}
Maintenant :
- TypeScript connaît le type exact
- L'autocomplétion fonctionne
- Les typos sont détectées à la compilation
- Les données mal formées sont filtrées
6. Le problème des type guards manuels
Pour un objet à 3 propriétés, ça va. Mais quand tu en as 15, 20, 30... ça devient vite laborieux et source d'erreurs.
C'est là qu'intervient Zod 😍.
7. Zod : la validation de schéma simplifiée
Zod te permet de définir un schéma et d'en dériver automatiquement le type TypeScript :
import {z} from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
// Le type est inféré automatiquement
type User = z.infer<typeof UserSchema>;
Validation avec parse
export class HttpUserAdapter {
getUsers(): Observable<User[]> {
return this._http.get<unknown[]>('https://jsonplaceholder.typicode.com/users').pipe(
map(results => z.array(UserSchema).parse(results.filter(isUser)))
);
}
}
Si les données ne correspondent pas au schéma, Zod lance une erreur explicite :
ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": ["email"],
"message": "Required"
}
]
Tu sais exactement quel champ pose problème, sur quel objet, et pourquoi.
Validation douce avec safeParse
Si tu préfères gérer les erreurs sans exception :
const result = UserSchema.safeParse(data);
if (result.success) {
// result.data est typé User
console.log(result.data.email);
} else {
// result.error contient les détails
console.error(result.error.issues);
}
Filtrer les données invalides
export class HttpUserAdapter {
getUsers(): Observable<User[]> {
return this._http.get<unknown[]>('https://jsonplaceholder.typicode.com/users').pipe(
map(results =>
results
.map(item => UserSchema.safeParse(item))
.filter((result): result is { success: true; data: User } => result.success)
.map(result => result.data)
)
);
}
}
8. Bonus Angular : httpResource avec parser intégré
Pour ceux qui utilisent httpResource (experimental), la validation s'intègre directement :
import {httpResource} from '@angular/common/http';
@Component({
template: `
@if (usersResource.value(); as users) {
@for (user of users; track user.id) {
<div>{{ user.name }} - {{ user.email }}</div>
}
}
`
})
export class UserList {
protected readonly usersResource = httpResource<User[]>({
url: 'https://jsonplaceholder.typicode.com/users',
parse: z.array(UserSchema).parse,
});
}
Le parser est appelé automatiquement sur la réponse. Si les données sont invalides, tu le sais immédiatement.
9. Récapitulatif
| Approche | Avantages | Inconvénients |
|---|---|---|
any |
Aucun | Tout : pas de typage, pas d'autocomplétion, bugs silencieux |
unknown + type guard manuel |
Contrôle total, zero dépendance | Verbeux pour les gros objets |
| Zod | Concis, messages d'erreur clairs, type inféré | Dépendance externe |
| Zod mini | Comme Zod, bundle réduit | API légèrement restreinte |
10. La règle à retenir
any, c'est interdit.
Ce n'est pas une question de purisme. C'est une question de professionnalisme. Tu ne codes pas seul dans ta grotte. Ton code sera relu, maintenu, étendu par d'autres (ou par toi dans 6 mois, ce qui revient au même).
Les outils existent. unknown est natif. Zod prend 5 minutes à configurer. Il n'y a plus d'excuse.
11. Optimisation : zod/mini pour réduire le bundle
Si jamais tu souhaites optimiser la taille de ton bundle tout en utilisant Zod, voici ce que je peux te proposer :
🔗 https://www.easyangularkit.com/blog/optimisation-bundle-zod-mini