Sådan afmeldes automatisk i Angular 9

I en tidligere artikel kiggede vi på de forskellige strategier for, hvordan man slipper af med den typiske afmeldings kedelplade i vores komponentklasser for at gøre vores projekt mere skalerbart. At læse denne artikel først kunne være en god ide at forstå denne fuldt ud, men det er ikke vigtigt.

Bare for at sammenfatte, dette er den slags kedelplade, vi taler om:

Målet er at fjerne disse 3 ting fra enhver komponentklasse i vores projekt:

  1. ødelagt $ = nyt emne ();
  2. this.destroyed $ .next ();
  3. ngOnDestroy (): annulleret {}

Naturligvis skal nummer 3 kun fjernes, hvis vi ikke behøver at udføre andre handlinger, når vi ødelægger komponenten. I så fald skal vi være i stand til at tilføje mere funktionalitet til metoden.

Dette er de strategier, der er foreslået i den forrige artikel:

  • Abstrakt klasse: god og enkel tilgang. Opfylder 1, 2 og 3, men det kan ikke skaleres, fordi en Typescript-klasse kun kan udvides fra nøjagtigt en anden klasse.
  • Mixin klasse: ligesom den abstrakte klasse, men skalerbar. En komponentklasse kan udvide flere mixin-klasser. Kun et advarsel: det fungerer ikke i Angular 8 med AOT-samling. Det fungerer dog i Angular 9, så det er fint, hvis du bare ikke bruger Angular 8.
  • Metoddekoratør: det fungerer i vinkel 8 men ikke i vinkel 9 og det tvinger os til at skrive ngOnDestroy () i hver komponent.

Vedbend

Men hvorfor mislykkes den sidste strategi i Angular 9. Hvad er problemet? Nå, dybest set denne version af rammen muliggør Ivy renderer som standard, og i denne nye kontekst er vores projekt ved at blive bygget ved hjælp af 2 kompilatorer:

  • Vinkelkompatibilitetskompilator eller ngcc: den behandler koden fra node_moduler for at producere den tilsvarende Ivy-version. Denne samling er nødvendig for at opnå bagudkompatibilitet.
  • Angular Typescript-kompilator eller ngtsc: den transpilerer vores Angular Typescript-kode til JavaScript og genanvender kantede dekoratører til statiske egenskaber. Du kan forstå det som et indpakning eller den normale Typescript-kompilator (tsc), der indeholder et sæt vinkeltransformationer.

Nu er dette ngtsc grunden til, at vores metodedekoratør ikke fungerer som forventet. I stedet for at få en normal Typescript-kompilering har vi noget lidt anderledes.

Lad os logge et øjeblik på vores metodeindretningsmål, som er selve komponenten. I Angular 8 får du denne output:

Så langt så godt. Lad os nu se, hvad der sker, hvis vi gør det samme i Angular 9:

Interessant. Nu får vores komponentkonstruktør et par nye ting. Men husk disse 2:

  • ɵcmp: Ivy's komponentdefinition.
  • ɵfac: Ivy's komponentfabrik.

Så du, at begge starter med den samme mærkelige karakter? Det er det græske bogstav theta, og det indikerer os, at disse egenskaber hører til Ivy's private API. Det betyder, at der absolut ikke er sikkerhed for, at disse navne forbliver. Tanken er, at så snart vi ved, at Angular 9 Ivy er fuldstændigt bagudkompatible, vil disse egenskaber droppe thetaen og blive simpelthen cmp og fac, men i øjeblikket kan vi ikke stole på den navnekonvention.

Uanset hvad de kaldes i fremtiden, vil disse 2 fyre give Angular mulighed for at udnytte kraften i komponenter med højere orden, og det fører os til vores fjerde strategi for at fjerne kedelpladen uden afmelding.

Udelukkende dekoratør

Konceptet med højere ordenskomponenter, eller kort fortalt, kommer fra React-verdenen. I stedet for at bruge arv som i vores 2 første strategier, ændrer hocs en komponents opførsel. I typeskrift kan dette smukt opnås gennem klassedekoratører, og vi er ved at se den rolle, Ivy spiller i alt dette.

ANSVARSFRASKRIVELSE: denne teknik er yderst eksperimentel og er derfor ikke produktion klar. Det er endnu ikke testet i et rigtigt projekt (dato 20200204) og afspejler ikke Angular teams officielle vision.

Vi talte lidt om metodedekoratører i den forrige artikel. I dette tilfælde bruger vi en klassedekoratør, der kun tager 1 argument: målet. I vores tilfælde kalder vi det cmpType, fordi det refererer til vores komponent.

Den første ting, vi skal gøre, er at hente de egenskaber, der er nævnt ovenfor: cmp og fac. For at nå denne ambition opretter vi en getComponentProp-hjælpefunktion. Og det er vigtigt, at vi kaster en fejl i tilfælde af, at disse egenskaber ikke findes i vores komponentkonstruktør, da denne API er privat og måske skal omdøbes eller ændres i fremtiden.

Men det er ikke alt. Vi bliver nødt til at ændre 2 egenskaber, der tilhører cmp: fabrik og onDestroy. Problemet er, at disse er readonly i den vinklede kerne. Så vi opretter vores eget ComponentDef-interface til at tilsidesætte det. Både modeller og redskaber skal flyttes til deres egne filer i et rigtigt projekt, men med henblik på denne artikel er alt placeret i den samme.

Nu gemmer cmpOnDestroy den originale onDestroy. Det har vi brug for senere.

Og nu kommer det det virkelige trick: vi erklærer en klasse med det ødelagte $ RxJS-emne og får det til at udvide den originale komponent. Dette er meget usædvanligt. Teoretisk set skal komponenter være dem, der udvides og implementeres og ikke omvendt. Og vi bryder også den gyldne regel om ikke at bruge arv i hocs. Men vi var nødt til at have den samme klasse med en ekstra ejendom.

En god ting, vi kan gøre i dette tilfælde, er at bevare navnet på den originale komponentfabrik. Det er bare en navngivet funktion, f.eks. MyComponent_Factory, der returnerer en ny forekomst af komponentklassen. Så vi bruger renameFunction-hjælpefunktionen til det. Returneringsværdien er vores nye fabrik.

const newCmpFactory = renameFunction (() => ny CmpTypeWithDestroyed (), fac.name);
cmp.factory = newCmpFactory;

Vi har så ændret cmp.factory og takket være at en ny forekomst af vores komponent vil have den ødelagte $ ejendom inkluderet. Men vi er stadig nødt til at afmelde og til det er vi også nødt til at ændre cmp.onDestroy, som vi forudsagde.

Her svarer implementeringen til den, vi har brugt i metodedekoratøren. Vi kalder .næste () og vi udløser den originale onDestroy, men denne gang gør vi det betinget, da det i dette tilfælde ikke er obligatorisk, at ngOnDestroy () -metoden er til stede i komponentklassen.

Implementeringen er utroligt elegant:

@Usubscribber () @Component ({}) eksportklasse MyContainerComponent {}

Og med dette opfylder vi punkterne 1, 2 og 3 som nævnt i begyndelsen samt valgfrit at være i stand til at udføre handlinger i ngOnDestroy ().

Konklusion

Efter at have taget et kig på alle mulighederne, og selvom denne sidste tilgang er mest spændende, er den ydmyge mening fra forfatteren af ​​denne artikel, det sikreste at bruge metodedekoratøren, hvis vi arbejder i Angular 8 og mixin-klassen, hvis vi er i vinkel 9. Der er endnu meget, der skal diskuteres om metaprogrammering med Ivy, men denne Unsubscribber hoc giver os et glimt af, hvad der kommer næste, og mulighederne ser ud til at være enorme.

Leg med det

Du kan teste de første 3 strategier i Angular 8 med denne repo:

https://github.com/kaplan81/auto-unsubscribe-ng8

Og hoc i Angular 9 plus de andre 3 med denne anden repo:

https://github.com/kaplan81/auto-unsubscribe-ng9

Du finder instruktion i README-filerne.

BEMÆRK (dato 20200210): den første bruger vinkel 8.3.24 og den anden vinkel 9.0.0.