I Angular-applikationer, hvor modulær opbygning og lazy-loading er afgørende for skalerbarhed og vedligeholdelse, er det essentielt at forstå, hvordan konfigurationer – herunder theming – håndteres på forskellige niveauer af applikationens struktur. Et særligt fokusområde er brugen af provider scopes, som gør det muligt at definere og isolere services og værdier til forskellige dele af applikationen uden at skabe utilsigtede bivirkninger.

Når et tema skal gøres tilgængeligt globalt i applikationen, kan man benytte application module scope til at udstille en standardkonfiguration via Angulars dependency injection-system. Eksempelvis defineres et themeToken, som anvendes til at injicere en ThemeService med et bestemt temaobjekt. Dette gøres i AppModule og gør, at alle komponenter og moduler, der arver fra root-context, anvender denne standard.

Men Angulars fleksibilitet tillader også, at man specificerer andre temaer i lazy-loaded moduler. Ved at angive et andet temaobjekt i providers-arrayet i f.eks. LoginModule, skabes der en separat instans af ThemeService, som kun anvendes i dette modul. Resultatet er, at login-komponenten kan have et helt andet visuelt udtryk – fx et metallic tema – mens resten af applikationen forbliver grøn.

Denne mekanisme understøttes desuden på komponentniveau. Ved at bruge Angulars @HostBinding kan man injicere dynamiske CSS custom properties direkte i komponentens stilkonfiguration. I LoginComponent anvendes fx themeService.getSetting('background') til at sætte baggrunden via CSS-variablen --background, hvilket muliggør en høj grad af visuel fleksibilitet uden behov for globale stylesheets.

Ud over visuelle temaer udnytter applikationen platformens provider scope til deling af tilstand, som fx brugersessioner og autentifikation. AuthService er her markeret med providedIn: 'platform', hvilket tillader global adgang til brugerens loginstatus og token, uanset hvor i applikationen man befinder sig. Denne tilgang gør det også muligt at reagere på login-events på tværs af moduler – som når brugeren automatisk navigeres til en bestemt kursusside efter login baseret på tidligere præferencer gemt i PreferenceService.

En videre anvendelse af platform scope ses i integrationen med Angular Elements. Her anvendes komponenter som TweetCourseComponent, der eksponeres som custom HTML elements og kan bruges uden for Angular-applikationens grænser – fx i statiske HTML-sider. Disse komponenter henter data via services injiceret med platform scope, hvilket betyder, at selv uden for Angulars kontekst kan man udnytte applikationens forretningslogik og præsentationslag.

I dette eksempel tilføjes Angular Elements via CLI og en specifik komponent registreres manuelt som et custom element gennem createCustomElement, hvilket muliggør, at Angular-komponenter som TweetCourseComponent kan anvendes som web components. Det er netop dette samspil mellem Angulars DI-system og webens åbne standarder, der gør teknologien velegnet til både mikrofrontends og modulær integration.

Det er vigtigt at forstå, at denne fleksibilitet kræver præcis styring af scope og afhængigheder. Genbrug af services på tværs af scopes uden bevidst arkitektur kan føre til utilsigtet deling af tilstand, hvilket i praksis kan skabe komplekse bugs og vedligeholdelsesproblemer. Et tydeligt skel mellem application scope, module scope og platform scope er derfor ikke blot en anbefaling, men en nødvendighed.

Endvidere bør læseren være bevidst om performanceimplikationerne ved lazy-loading. Hver gang et modul lazy-loades og har egne providers, initialiseres services og værdier på ny. Dette kan være ønsket i theming-kontekst, men uhensigtsmæssigt hvis der opstår redundans. Brug af singleton-services via platform scope kan afhjælpe dette, men bør implementeres med omtanke.

Særligt når Angular benyttes i større teams eller i applikationer med mange feature-moduler, bliver provider scopes ikke kun en teknik – men en arkitektonisk disciplin, som afgør applikationens robusthed, fleksibilitet og fremtidige udviklingsmuligheder.

Hvordan opdaterer man korrekt Angular-projekter og deres afhængigheder?

Når man opdaterer Angular-projekter, er det afgørende at forstå, hvordan Angular håndterer afhængigheder og migreringer. Angular’s opdateringsproces favner både officielle framework-pakker og tredjepartsbiblioteker. Den anvendte kommando, ng update, søger efter automatiserede migreringer i pakkernes metadata og anvender dem under opdateringen, men det er ikke altid komplet dækkende. Nogle migreringer findes i dokumentationen, men ikke i Angular Update Guide – og omvendt.

Afhængigheder som RxJS, tslib og Zone.js spiller en kritisk rolle i Angular-økosystemet. Historisk set har Angular-opdateringer håndteret versioneringen af disse, men migreringer ved større ændringer er ikke altid tilgængelige. Fx findes der ingen automatiseret overgang fra RxJS version 6.x til 7.x. Det samme gælder for TypeScript, som ved hver mindre opdatering kan introducere brydende ændringer uden tilhørende migrationsværktøjer. Zone.js, som indirekte anvendes gennem Angulars NgZone, kan ændre importstier mellem versioner – fx i version 0.11.1 – og kræver manuel kontrol, selvom man sjældent bruger det direkte.

Angular CLI understøtter to stabile Node.js-versioner pr. release. Versioner med ulige numre – altså ustabile – er ikke understøttet. Ved migrering fra Angular 11 til 12 mister man fx understøttelse for Node.js 10 og opnår i stedet support for Node.js 14.15+ og 12.14+.

For at udnytte ng update optimalt anbefales det at opdatere én major-version ad gangen og benytte flaget --create-commits for at spore ændringer via Git. Det gør det muligt at cherry-picke migreringer eller tilbagerulle selektivt. Vil man køre en bestemt migration igen, anvendes --migrate-only med migreringsnavnet, som findes i Git-commits’ beskeder.

Eksempel:

bash
ng update @angular/cli@^12 --migrate-only production-by-default

Dette aktiverer en frivillig migration, der gør produktionsbuild til standard. Mange automatiserede migreringer henviser til websider, hvor rationale og kodeeksempler før/efter forklares – særligt nyttigt ved manuelle tilpasninger.

Flere centrale Ivy-migreringer har særlig betydning. Fx workspace-version-9, som ændrer AOT-kompilering til true som standard, også i udviklingskonfigurationer. Desuden justeres include-feltet i tsconfig.app.json til "src/**/*.d.ts" for at sikre korrekt typeopdagelse.

Migreringen lazy-loading-syntax omskriver den forældede string-baserede lazy loading:

ts
loadChildren: './dashboard.module#DashboardModule'

til den moderne, understøttede form:

ts
loadChildren: () => import('./dashboard.module').then(m => m.DashboardModule)

String-baseret lazy loading er udfaset og må undgås.

En anden vigtig Ivy-migrering er migration-v9-dynamic-queries, som ændrer behandlingen af ViewChild og ContentChild. I Angular 8 blev det påkrævet at angive static, mens det fra version 9 er gjort valgfrit med default false. Dette kan ændre komponenters adfærd, især hvis man afhænger af timing i DOM-adgangen.

ts
@ViewChild('greeting', { static: true }) greetingElement?: ElementRef;

Ved uopmærksomhed her kan man opleve fejl i renderingsrækkefølger og afhængigheder, der pludseligt ikke findes ved runtime. Automatiserede migreringer dækker disse, men kræver stadig faglig opmærksomhed og kodegennemgang.

Det er vigtigt at bemærke, at selvom Angular tilbyder værktøjer til opdatering og migrering, er der ingen garanti for fuld kompatibilitet uden manuel indsats. Kombinationen af versionsstyring, ændrede API’er og indirekte afhængigheder kan føre til uforudsete konflikter. En opdatering skal derfor altid følges af test, gennemgang af changelogs og eventuelt manuelle korrektioner.

Hvordan håndteres ViewChild, migrations og optimeringer ved overgang fra Angular View Engine til Ivy?

I overgangen fra Angular View Engine til Angular Ivy har håndteringen af @ViewChild-dekorationen gennemgået væsentlige ændringer, særligt med introduktionen og brugen af static-optionen. Før Angular 8 blev @ViewChild brugt uden denne option, for eksempel:

typescript
@ViewChild('error') errorElement?: ElementRef;
@ViewChild('greeting') greetingElement?: ElementRef;

Med Angular 8 View Engine blev static-flaget obligatorisk, og det skulle specificeres, hvorvidt view queries skulle være statiske eller dynamiske:

typescript
@ViewChild('error', { static: false }) errorElement?: ElementRef;
@ViewChild('greeting', { static: true }) greetingElement?: ElementRef;

Denne migration forsøger at gætte den mest passende static-værdi ud fra konteksten, hvor f.eks. elementer inde i embedded views som følge af strukturelle direktiver bliver dynamiske ({ static: false }).

Fra Angular 9 med Ivy blev static-optionen valgfri, men den defaultede til false for dynamiske queries, og den blev automatisk fjernet for dem, som stadig var dynamiske. Statisk forespørgsel vedblev at kræve en eksplicit angivelse. Det er vigtigt at notere, at QueryLists ikke berøres af denne migration, da de altid er dynamiske. Det anbefales at gennemgå sine queries nøje før migration, med særlig fokus på de officielle migrationsguides for statiske og dynamiske queries.

En anden vigtig ændring ved migrationen til Angular Ivy handler om testfunktioner. Den tidligere async testwrapper blev omdøbt til waitForAsync for at undgå forvirring med async-await. Denne nye wrapper sikrer, at alle mikro- og makrotasks er afsluttede, inden testkaldet færdiggøres, hvilket forbedrer testens præcision og læsbarhed.

Migrationsværktøjet identificerer også klasser, der mangler @Injectable-dekorationen, især dem, der anvendes som class-baserede providers i Angular-moduler. Disse klasser får automatisk tilføjet @Injectable for at sikre korrekt injektion. Endvidere ændres ufuldstændige providers, som Angular View Engine tidligere håndterede som class providers, til værdibaserede providers med undefined som værdi i Ivy. Dette kan medføre utilsigtet adfærd, og man bør derfor gennemgå alle providers, som efter migrationen indeholder useValue: undefined.

Yderligere optional migration inkluderer opdatering af Angular CLI workspace-konfigurationer til at bruge produktionsmode som standard. Fra version 12 behøver man ikke længere at angive --configuration=production ved build, men eksisterende projekter kræver en eksplicit migration for at aktivere denne nye default.

Ud over automatiserede migrationer anbefales det at foretage manuelle justeringer for at tilpasse applikationen til kommende Angular-versioner. Det gælder blandt andet ændringer i initialNavigation for RouterModule, hvor gamle værdier som true, false, og 'legacy_enabled' er fjernet eller forældede, og nye værdier som 'enabledBlocking' og 'enabledNonBlocking' introduceres med forskellige timingstrategier for initial navigation, især relevant for server-side rendering med Angular Universal.

Desuden kan change detection optimeres ved brug af nye ngZone-bootstrapmuligheder: ngZoneEventCoalescing og ngZoneRunCoalescing. Disse indstillinger gør det muligt at samle flere change detection-kørsler, som sker i samme VM-tur, til én enkelt operation synkroniseret med browserens animationsframe, hvilket reducerer unødvendige detektioner ved f.eks. multiple klik-event handlers. Det betyder en mere effektiv opdatering og forbedret ydelse i komplekse applikationer.

Det er vigtigt at forstå, at migrationerne ikke kun handler om syntaxændringer, men også om at tilpasse applikationens arkitektur og performanceadfærd til Ivy's moderne runtime. At overse eller misforstå migrationsdetaljerne kan føre til skjulte fejl, ineffektive providers eller testproblemer. En grundig revision og testning efter migrationen er derfor afgørende for at sikre applikationens stabilitet og ydeevne.