Un signal est une valeur réactive synchronisée, qui :
- stocke une valeur
- notifie ses dépendants dès qu’elle change
- se lit comme une fonction :
mySignal()
Exemple :
const count = signal(0);
count(); // lecture
count.set(1); // écriture👉 C’est simple, prévisible, et synchrone.
Pas d’abonnements, pas d’async pipe, pas de zones, pas de rebond inutile.
const name = signal("Seb");
name.set("Sébastien");const fullname = computed(() => `${firstname()} ${lastname()}`);effect(() => console.log("Count changed:", count()));input() remplace @Input() par un signal d’entrée, fourni par le parent.
readonly user = input<User>();
Les valeurs provenant de input() sont READONLY (conceptuellement).
Quand tu écris :
readonly addresses = input<Addresses>();Cela veut dire :
- le parent contrôle la donnée
- le composant lit la donnée
- le composant ne la modifie jamais
C’est un contrat clair :
➡️ input = source externe, jamais modifiée
➡️ signal = source interne modifiable
C’est LE point le plus souvent mal compris.
this.addresses.set(...)this.addresses().city = "Paris"; ➡️ Pourquoi ?
- dirty detection impossible
- template ne se met pas à jour
- mutation silencieuse
- violation du contrat parent → enfant
- débogage infernal assuré
Toujours :
const a = this.addresses();Méthode recommandée Angular :
readonly shippingAddress = computed(() =>
this.addresses()?.shippingAddress ?? null
);readonly addresses = input<Addresses>();
private localAddresses = signal<Addresses>(new Addresses());
effect(() => {
const incoming = this.addresses();
if (incoming) {
this.localAddresses.set(structuredClone(incoming));
}
});Puis :
this.localAddresses.mutate(a => {
a.shippingAddress = ...
});readonly addresses = input<Addresses>(() => new Addresses());➡️ Factory pure, aucune mutation partagée, propre.
readonly addresses = input<Addresses>(new Addresses());➡️ Passe la même instance partout → piège de mutabilité.
- pas de réactivité fine
- pas de dérivations simples
- change detection imprévisible
- besoin de lifecycle hooks
- type‑narrowing fragile
- réactivité fine‑grainée
- lecture simple
user() - valeurs dérivées en
computed() - aucune magie
- predictable flow
- code plus propre et plus testable
Le signal ne déclenche rien si tu modifies l’objet interne :
const user = signal({name: "Seb"});
user().name = "Sébastien"; // ❌ → effect ne détecte rienPourquoi ?
➡️ parce que le signal observe la référence, pas le contenu.
📌 Toujours utiliser set() ou update() pour les signaux modifiables.
| Concept | À faire | À ne pas faire |
|---|---|---|
| lecture | addresses() |
addresses |
| modification | set/update/mutate (mais pas sur un input) |
mutation directe |
| input() | readonly, non modifié | modifié, muté, remplacé |
| default value | factory () => new Value() |
new Value() directement |
| template | capturer dans une const locale | appeler 10 fois signal() |
readonly addresses = input<Addresses>();
readonly shippingAddress = computed(() => {
const a = this.addresses();
return a?.shippingAddress ?? this.legalUnit?.address ?? null;
});Template :
Clair. Lisible. Zéro mutation. Zéro bug.