AngularAtlaséd. 22.2

Chap. 16UI avancée et performance71 / 75

Fuites mémoire : les trouver et les corriger

Abonnements, timers et listeners qui survivent à leur composant — comment les repérer dans les DevTools et les fermer.

Une fuite de mémoire dans une application Angular fait rarement planter quoi que ce soit. L’onglet devient juste plus lent et plus lourd au fil de l’après-midi : chaque fois qu’un composant est détruit, quelque chose qu’il a lancé continue de tourner — un abonnement, un timer, un listener sur window — et garde tout le composant en vie avec lui. Ce sont les utilisateurs qui laissent l’application ouverte toute la journée qui paient.

Cette page montre les sources derrière presque toutes les fuites, comment fermer chacune d’elles et comment retrouver celles qui vous ont échappé avec Chrome DevTools. Le labo crée de vraies fuites dans cet onglet et les nettoie quand vous quittez la page.

Pourquoi c’est important
Détruire un composant n’arrête pas ce qu’il a lancé. Angular retire le DOM et exécute les hooks de destruction ; tout ce qui est référencé depuis un endroit qui vit plus longtemps — un service root, un timer, un listener global — pointe toujours vers le composant et vers tout ce vers quoi le composant pointe.

Les suspects habituels

Ce widget a l’air inoffensif et contient les trois fuites classiques. Chaque callback capture this, et chacun est retenu par quelque chose qui vit plus longtemps que le widget : un service root, la liste des timers du navigateur, window. Ouvrez-le et fermez-le quelques fois dans le labo et regardez ce qui reste.

@Component({ selector: 'app-price-widget', template: `{{ price() }}` })
export class PriceWidget implements OnInit {
  private readonly prices = inject(PriceFeed); // un service root : vit aussi longtemps que l’application
  protected readonly price = signal(0);

  ngOnInit() {
    // ✗ 1. le flux garde le callback, le callback garde `this`, pour toujours
    this.prices.updates$.subscribe((p) => this.price.set(p));

    // ✗ 2. le timer continue de tourner après la disparition du composant
    setInterval(() => this.refresh(), 5000);

    // ✗ 3. window survit à tous les composants
    window.addEventListener('resize', () => this.layout());
  }
}
LiveOuvrir et fermer un widget qui oublie de nettoyer
Fermer au destroy :

Aucun widget sur la page.

détruits, encore en vie
0
mémoire retenue
0.00 MB
travail des zombies
0/s
abonnements
0
timers
0
listeners
0
widgets créés
0
Appuyez sur « Ouvrir et fermer 10 fois », puis bougez la souris sur la page : les widgets détruits continuent de travailler. Cochez les trois cases, recommencez, et les compteurs restent à zéro.

Chaque widget garde un buffer de 256 Ko, donc le nombre « retenu » est de la mémoire qui ne peut vraiment pas être libérée tant que son zombie vit. Les cases s’appliquent aux widgets créés après les avoir cochées ; « Nettoyer les zombies » arrête ce que les anciens ont laissé derrière eux.

Fermer chaque source

La règle est courte : pour tout ce que vous lancez, enregistrez un arrêt au moment même où vous le lancez. Pour les observables, Angular a deux outils qui le font pour vous : toSignal() et takeUntilDestroyed() ; pour le reste, il y a DestroyRef.onDestroy(). Les listeners déclarés dans host sont retirés par Angular ; pour plusieurs listeners sur des objets globaux, un AbortController les ferme tous en une ligne.

import { Component, DestroyRef, inject, signal } from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-price-widget',
  template: `{{ price() }}`,
  host: { '(window:resize)': 'layout()' }, // ✓ 3. Angular retire lui-même les host listeners
})
export class PriceWidget {
  private readonly prices = inject(PriceFeed);
  private readonly destroyRef = inject(DestroyRef);

  // ✓ 1a. toSignal se désabonne quand le composant est détruit
  protected readonly price = toSignal(this.prices.updates$, { initialValue: 0 });

  constructor() {
    // ✓ 1b. ou, quand vous avez vraiment besoin de subscribe() : takeUntilDestroyed
    this.prices.alerts$.pipe(takeUntilDestroyed()).subscribe((a) => this.notify(a));

    // ✓ 2. chaque timer a son « stop » enregistré à côté de son « start »
    const id = setInterval(() => this.refresh(), 5000);
    this.destroyRef.onDestroy(() => clearInterval(id));
  }
}
SourceCe qui la garde en vieLe remède
subscribe()la source garde votre callback, le callback garde le composanttoSignal(), le pipe async ou takeUntilDestroyed()
setIntervalle navigateur garde le callback jusqu’à ce que le timer soit effacéDestroyRef.onDestroy(() => clearInterval(id))
addEventListenerwindow et document survivent à tous les composantshost: { '(window:resize)': … }, Renderer2.listen ou un AbortController
caches dans les servicesun service root vit aussi longtemps que l’application, et sa Map aussisupprimer l’entrée au destroy, ou utiliser une WeakMap indexée par l’élément
widgets tiersles graphiques, cartes et éditeurs enregistrent leurs propres listeners et timersappeler leur destroy() / remove() dans onDestroy
// ✗ un service root qui retient des composants (ou leur DOM) les garde en vie
@Service()
export class Tooltips {
  private readonly open = new Map<string, TooltipComponent>(); // jamais supprimé → fuite
}

// ✓ supprimer au destroy, ou laisser le garbage collector décider avec une WeakMap
@Service()
export class Tooltips {
  private readonly anchors = new WeakMap<HTMLElement, TooltipComponent>(); // clé disparue → entrée disparue
}

Ce qui ne fuit pas

Tous les subscribe() ne posent pas problème. Les requêtes HttpClient se terminent d’elles-mêmes après la réponse (avec takeUntilDestroyed(), une requête lente est aussi annulée quand vous partez). Le pipe async, toSignal(), output() et les host listeners nettoient derrière eux, tout comme effect() et computed() créés dans un composant. Le Router nettoie les observables d’ActivatedRoute. Ce qui fuit, c’est un abonnement à quelque chose qui ne se termine jamais et vit plus longtemps que vous : un store, un Subject dans un service root, interval, fromEvent(window, …), les événements du router.

Trouver les fuites avec Chrome DevTools

Une fuite, c’est un nombre qui augmente alors qu’il devrait rester stable. La méthode : répéter une action qui ne devrait rien laisser derrière elle — ouvrir et fermer un dialogue, aller sur une page et revenir — et comparer la mémoire avant et après.

  1. Dans le laboratoire ci-dessus, décochez les trois correctifs. Ouvrez DevTools → le panneau Memory → sous Select profiling type, choisissez Heap snapshot. Cliquez sur l’icône de corbeille (Collect garbage), puis sur Take snapshot.
  2. Appuyez sur Ouvrir et fermer 10 fois et attendez la fin. De nouveau Collect garbage et Take snapshot.
  3. Snapshot 2 sélectionné, passez le menu en haut à gauche de Summary à Comparison (par rapport à Snapshot 1) et triez par # Delta.
  4. Tapez app-leaky-widget dans Filter by class : +10, un élément hôte par widget fermé. Puis DOMTimer (+10 timers qui tournent encore) et EventListener (+10 listeners). Sur le site publié, les noms de classes sont minifiés : LeakyWidget n’apparaît que si vous lancez l’application en développement.
  5. Cliquez sur une instance et lisez le volet Retainers en bas : dépliez les lignes pour remonter la chaîne jusqu’à ce qui la garde en vie. Appuyez sur Nettoyer les zombies, prenez un troisième snapshot, et les mêmes lignes apparaissent sous # Deleted.
  6. Plus direct : sous Select profiling type, choisissez Detached elements et appuyez sur Get detached elements. Vous obtenez un <app-leaky-widget> par widget fermé.

Pour une vérification rapide sans snapshot : menu de commandes (Ctrl/Cmd+Shift+P) → Show Performance monitor. Surveillez JS event listeners : il augmente de 10 à chaque « 10 fois » correctifs décochés et ne redescend qu’après le nettoyage.

Tous les panneaux, pas à pas, avec exercices et pièges (par exemple DevTools qui garde lui-même des objets en vie), sont dans DevTools : regarder dans votre application.

Un test qui attrape la fuite la prochaine fois

Une fois une fuite corrigée, rendez son retour impossible. Un Subject sait si quelqu’un est encore abonné (observed) ; après fixture.destroy(), la réponse doit être false. Plus de détails sur les tests dans tester avec Vitest.

import { TestBed } from '@angular/core/testing';
import { describe, expect, it } from 'vitest';

describe('PriceWidget', () => {
  it('stops listening to the feed when destroyed', () => {
    const feed = TestBed.inject(PriceFeed); // updates$ est un Subject dans le service
    const fixture = TestBed.createComponent(PriceWidget);
    fixture.detectChanges();
    expect(feed.updates.observed).toBe(true);

    fixture.destroy();
    expect(feed.updates.observed).toBe(false); // plus personne n’écoute
  });
});

Quand OUI, quand NON

Ça vaut la chasse
  • Les onglets qui restent ouverts des heures : tableaux de bord, outils de back-office, caisses, chat.
  • Les composants créés et détruits souvent : dialogues, lignes de liste, onglets, routes visitées à répétition.
  • Le code qui s’abonne à des choses globales : stores, WebSockets, événements window, événements du router.
Pas de quoi s’inquiéter
  • Une requête HttpClient ponctuelle : elle se termine d’elle-même.
  • Les abonnements via le pipe async ou toSignal() : ils sont nettoyés pour vous.
  • Un service root abonné à quelque chose pendant toute la vie de l’application : c’est voulu, pas une fuite.

Pièges fréquents

  • subscribe() dans ngOnInit, sans teardown, à quelque chose venant d’un service root.
  • Faire confiance à take(1). Si la source n’émet jamais — l’utilisateur part avant la première valeur —, l’abonnement attend pour toujours.
  • removeEventListener avec une autre fonction. .bind(this) crée une nouvelle fonction à chaque fois, donc rien n’est retiré.
  • Des composants ou des nœuds DOM dans une Map de service. Le service vit pour toujours, et eux aussi.
  • Mesurer sans lancer le garbage collector d’abord. Le heap monte et descend tout seul ; comparez des snapshots pris après Collect garbage.

Aide-mémoire

subscribe() à une source durable     takeUntilDestroyed() · toSignal() · pipe async
take(1) / first()                    sûr seulement si la source VA émettre ; sinon il attend pour toujours
setInterval / chaînes de setTimeout  DestroyRef.onDestroy(() => clearInterval(id))
listeners sur window / document      host: { '(window:resize)': … } · Renderer2.listen · AbortController
removeEventListener                  exige la MÊME fonction : garder la référence, pas deux .bind()
services root                        pas de Map de composants ou de nœuds DOM ; supprimer au destroy, ou WeakMap
widgets tiers                        chart.destroy(), map.remove() dans onDestroy
ne fuit pas                          HttpClient (se termine), pipe async, toSignal, outputs, host listeners
sûrs aussi                           effect() et computed() dans les composants : détruits avec eux
DevTools                             Memory → snapshot → répéter N× → GC → snapshot → Comparison
lire les retainers                   Subscriber / Timeout / EventListener = qui le garde en vie
"Detached"                           des nœuds DOM retirés de la page mais encore référencés
en CI                                subject.observed === false après fixture.destroy()

Chap. 16 · 0 sur 4 terminées

Leçon terminée ?

Marquez-la : elle apparaîtra sur votre carte, dans le sommaire et sur la page d’accueil.

Se marque toute seule une fois la leçon lue en entier.

  1. Web Workers
  2. Listes géantes : virtual scroll
  3. Animations modernes et View Transitions
  4. Fuites mémoire : les trouver et les corriger
Voir toute votre carte

Les 75 pages · tapez pour filtrer

  • 01Nouveautés d’Angular 21 & 22Tous les changements importants de la v21 et de la v22, en bref, avec un lien vers la page qui explique chacun.Départ
  • 01Votre première application, pas à pasUne liste de lecture construite d’un composant vide jusqu’à une application terminée — signals, @for, inputs et outputs, un service et un formulaire, pas à pas.Départ
  • 01Décisions rapides« Où mettre ça ? », « Signal ou Observable ? », « Shared ou feature ? » — des réponses courtes aux questions de tous les jours.Départ
  • 01Questions d’entretien Angular60 vraies questions d’entretien pour profils junior, confirmé et senior, avec la réponse à donner, la relance et les signaux d’alerte. Plus un entretien blanc.Départ
  • 02Standalone et bootstrapComment une application démarre sans NgModule : bootstrapApplication, app.config.ts, imports dans le composant.Fondamentaux modernes
  • 02Data binding et événementsInterpolation, bindings de propriétés et d’attributs, événements avec $event et filtres de touches, et comment [( )] est construit — ce qui entre dans le DOM et ce qui en revient.Fondamentaux modernes
  • 02Control Flow : @if, @for, @switchLa syntaxe de blocs qui remplace *ngIf/*ngFor/ngSwitch, avec track, @empty et vérifications d’exhaustivité.Fondamentaux modernes
  • 02@let et les nouvelles expressions de templateVariables locales avec @let, fonctions fléchées, spread, regex, instanceof, commentaires dans les balises.Fondamentaux modernes
  • 02@defer — Lazy Loading dans les templatesChargez les composants lourds uniquement quand c’est nécessaire : triggers, prefetch, placeholder, loading, error.Fondamentaux modernes
  • 02Bindings de classe et de stylePourquoi [class.x] et [style.x] ont remplacé ngClass et ngStyle (avec migration automatique depuis la v21).Fondamentaux modernes
  • 02Styles des composants et encapsulation:host, les trois modes d’encapsulation, les thèmes avec des variables CSS, et pourquoi ::ng-deep est un dernier recours.Fondamentaux modernes
  • 02Pipes : quand et commentPipes pures vs impures, pipes personnalisées, et comment choisir entre une pipe, computed() et une méthode.Fondamentaux modernes
  • 03signal() et computed()Les primitives réactives : état modifiable, état dérivé, égalité, untracked et règles d’immutabilité.Signals et réactivité
  • 03effect() — quand OUI, quand NONEffets de bord synchronisés avec les Signals : localStorage, logs, API impératives. Et leurs pièges.Signals et réactivité
  • 03linkedSignal() — état dérivé modifiableUn signal qui se réinitialise quand sa source change, mais que vous pouvez aussi modifier à la main.Signals et réactivité
  • 03resource, rxResource, httpResourceDes données asynchrones sous forme de Signals : status, value, error, reload, annulation automatique. Stable depuis la v22.Signals et réactivité
  • 03debounced() — signals différésLa version « debounce » d’un signal, sans RxJS. Parfait pour la recherche.Signals et réactivité
  • 03Listes : rechercher, filtrer, trier, paginerUne chaîne de signals computed qui ne recalcule que ce qui a changé, avec les filtres dans l’URL pour qu’un lien rouvre la même vue.Signals et réactivité
  • 03Les Observables depuis zéroCe qu’est un Observable, subscribe et unsubscribe, les opérateurs que vous utilisez chaque semaine, froid ou chaud — et quand un signal suffit.Signals et réactivité
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — et quand RxJS reste le bon choix.Signals et réactivité
  • 04input() et output()Les inputs comme Signals (required, transform, alias) et les outputs typés. Les données descendent, les événements remontent.Composants : communication
  • 04model() — binding bidirectionnelLe « banana-in-a-box » [( )] avec une seule API. Quand l’utiliser, et quand input + output est préférable.Composants : communication
  • 04viewChild et contentChildLes queries comme Signals : viewChild, viewChildren, contentChild, contentChildren, l’option read.Composants : communication
  • 04Lifecycle et DestroyRefQuels hooks comptent encore à l’ère des Signals, afterNextRender, DestroyRef et ngOnChanges typé.Composants : communication
  • 04Projection de contenung-content avec select, plusieurs slots, contenu par défaut et le piège de la projection conditionnelle.Composants : communication
  • 04ng-template et ngTemplateOutletLes templates comme valeurs : les render props en Angular, contexte typé et personnalisation des composants.Composants : communication
  • 05Smart vs DumbContainer vs présentation : qui détient l’état, qui se contente d’afficher, et comment bien les séparer.Conception des composants
  • 05Composants génériques <T>Un DataTable<T> ou un Select<T> correctement typé, avec des templates pour les cellules.Conception des composants
  • 05Composants dynamiquesNgComponentOutlet, createComponent avec inputBinding/outputBinding, et un tableau de bord construit à partir d’une configuration.Conception des composants
  • 05Directives et composition d’hôteLes directives d’attribut, les métadonnées host: {} et la Directive Composition API (hostDirectives).Conception des composants
  • 05Contrôles de formulaire personnalisésFormValueControl (Signal Forms) vs ControlValueAccessor : une notation par étoiles, dans les deux variantes.Conception des composants
  • 05Change DetectionOnPush est la valeur par défaut depuis la v22, Zone.js a disparu depuis la v21. Ce qui déclenche le rendu et comment éviter les mauvaises surprises.Conception des composants
  • 06inject() et @Service()Le nouveau décorateur @Service() de la v22, inject() vs le constructeur, et le contexte d’injection.Dependency Injection
  • 06Providers et hiérarchie des injecteursRoot vs route vs composant, useClass/useValue/useFactory/useExisting, et les modificateurs optional/self/skipSelf.Dependency Injection
  • 06InjectionToken et provideX()Configuration typée, fonctions provideFeature(), makeEnvironmentProviders et initializers.Dependency Injection
  • 06injectAsync() — services en Lazy LoadingChargez un service lourd uniquement quand vous en avez besoin, avec prefetch à l’idle.Dependency Injection
  • 07Structure des dossierscore / shared / features, ce que contient chacun, et à quoi ressemble l’intérieur d’une feature.Architecture d’application
  • 07Shared vs featureQuand déplacer un composant dans shared et (surtout) quand NE PAS le faire. Une checklist concrète.Architecture d’application
  • 07Interfaces et modèlesInterface en ligne ou fichier séparé ? DTO vs modèle de domaine, type vs interface, où les ranger.Architecture d’application
  • 07State Management avec les SignalsStores simples à base de Signals, facades, état local vs global, et quand NgRx devient nécessaire.Architecture d’application
  • 07Guide de style et nommageLes conventions officielles actuelles : noms de fichiers sans suffixes, protected, readonly, ordre des membres.Architecture d’application
  • 07Barrels, alias et frontièresindex.ts, alias de chemins, API publique d’une feature et règles de dépendances vérifiables automatiquement.Architecture d’application
  • 07Erreurs : @boundary et ErrorHandlerLe nouveau bloc @boundary de la v22.2, l’ErrorHandler global, les erreurs HTTP et les erreurs de resource.Architecture d’application
  • 08Routing moderne et Lazy LoadingloadComponent, routes enfants, paramètres comme inputs et héritage des paramètres (le nouveau comportement par défaut de la v22).Routing
  • 08Guards et resolverscanActivate, canMatch, canDeactivate et resolve sous forme de fonctions, avec redirections via UrlTree / RedirectCommand.Routing
  • 09Signal Forms — les basesDes formulaires bâtis sur les Signals : form(), [formField], validation par schéma. Stable depuis la v22.Formulaires
  • 09Signal Forms — avancéValidation personnalisée et inter-champs, async/HTTP avec debounce, listes dynamiques, désactivé/masqué, schémas réutilisables.Formulaires
  • 09Reactive Forms typésFormGroup typé, nonNullable, FormArray — et leur cohabitation avec les Signal Forms.Formulaires
  • 10HttpClient, interceptors et couche d’APIHttpClient sans configuration (v21), Fetch par défaut (v22), interceptors fonctionnels et une couche d’API avec mapping des DTO.HTTP et données
  • 11Performance — une checklistCe qui compte vraiment : OnPush + Signals, track, @defer, routes en Lazy Loading, images, bundles et Hydration.Qualité et performance
  • 11Tests avec VitestVitest est le runner par défaut depuis la v21. TestBed, composants avec Signals, services, HTTP et harnesses.Qualité et performance
  • 11DevTools : regarder dans votre applicationChrome DevTools et Angular DevTools pas à pas : ce que montre chaque panneau, avec des exercices qui fonctionnent sur ce site.Qualité et performance
  • 11Angular AriaDes composants headless et accessibles que vous stylez comme vous voulez : tabs, accordion, menu, listbox...Qualité et performance
  • 12SSR, Prerendering et modes de renduQuand rendre sur le serveur, quand au build et quand uniquement dans le navigateur : RenderMode par route, app.routes.server.ts et les coûts réels.SSR et Hydration
  • 12Hydration : event replay et incrémentaleComment le navigateur reprend le HTML du serveur sans le re-rendre : Hydration complète, event replay, @defer (hydrate on …) et erreurs de mismatch.SSR et Hydration
  • 12Code compatible serveurwindow n’existe pas sur le serveur : PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, le cache HTTP et PendingTasks.SSR et Hydration
  • 13Micro frontendsQuand découper une application en plusieurs déploiements vaut le coup, et quand c’est juste de la complexité : Native Federation, Module Federation, iframes, Web Components.Architecture à grande échelle
  • 13Web Components avec Angular ElementscreateCustomElement : un composant Angular utilisé comme balise HTML n’importe où, plus la communication entre applications via des événements et BroadcastChannel.Architecture à grande échelle
  • 13Monorepo, bibliothèques et frontièresUn workspace avec plusieurs applications et bibliothèques : ng generate library, Nx, points d’entrée secondaires, publication et règles de dépendances.Architecture à grande échelle
  • 13Architecture hexagonale (ports et adapters)Un domaine sans Angular, des ports sous forme de classes abstraites et des adapters interchangeables : HTTP, localStorage ou en mémoire, sans toucher à l’UI.Architecture à grande échelle
  • 14Les Signals sous le capotLe graphe réactif : producteurs et consommateurs, push-pull, pourquoi computed est lazy et sans glitch, le problème du diamant et quand les effects s’exécutent.Réactivité avancée
  • 14RxJS avancé : opérateurs d’ordre supérieurswitchMap, mergeMap, concatMap et exhaustMap sur un diagramme interactif, plus retry avec backoff, shareReplay et comment éviter les fuites.Réactivité avancée
  • 14NgRx SignalStoreState Management à grande échelle sur les Signals : withState, withComputed, withMethods, rxMethod, entités et hooks — et quand un store simple suffit.Réactivité avancée
  • 14Machines à états avec les signalsFini les états impossibles : une machine typée sur des signals, avec transitions, gardes et un diagramme d’états en direct.Réactivité avancée
  • 15WebSockets : un temps réel qui résiste au réseauUn socket typé sous forme de signals : reconnexion avec backoff et jitter, heartbeat, file d’attente hors ligne et sécurité SSR.Temps réel et hors ligne
  • 15Mises à jour optimistes et rollbackUne UI instantanée qui reste honnête : snapshot et rollback, réponses dans le désordre, clés d’idempotence.Temps réel et hors ligne
  • 15Offline-first : Service Worker et cacheLe Service Worker d’Angular : stratégies de cache, annonce des nouvelles versions et file d’attente pour les modifications faites hors ligne.Temps réel et hors ligne
  • 16Web WorkersDes calculs lourds sans bloquer l’UI : un vrai worker dans la page, l’échange de messages et quand ça n’en vaut pas la peine.UI avancée et performance
  • 16Listes géantes : virtual scroll100 000 lignes sans latence : le virtual scroll du CDK, itemSize, trackBy et les alternatives (pagination, scroll infini).UI avancée et performance
  • 16Animations modernes et View Transitionsanimate.enter et animate.leave à la place de @angular/animations (déprécié), transitions CSS et View Transitions à la navigation.UI avancée et performance
  • 16Fuites mémoire : les trouver et les corrigerAbonnements, timers et listeners qui survivent à leur composant — comment les repérer dans les DevTools et les fermer.UI avancée et performance
  • 17Sécurité : XSS, sanitisation et CSPCe qu’Angular fait automatiquement contre le XSS, quand bypassSecurityTrust devient dangereux, plus la CSP avec nonce, Trusted Types et XSRF.Sécurité et internationalisation
  • 17Authentification : tokens et refreshUn interceptor qui ajoute le token, le renouvelle une seule fois sur une 401 et rejoue les requêtes en attente — plus où conserver le token.Sécurité et internationalisation
  • 17OAuth 2.1 et OpenID Connect avec PKCELe flux authorization code avec PKCE, pas à pas : verifier et challenge, state et nonce, l’ID token, et pourquoi un BFF est plus sûr.Sécurité et internationalisation
  • 17Internationalisation (i18n)$localize, l’attribut i18n, ICU plural/select, un build par langue vs des traductions à l’exécution, et le formatage selon la locale.Sécurité et internationalisation