I udviklingen af enterprise-applikationer er det nødvendigt at implementere en effektiv arkitektur, som både understøtter skalering og giver et klart billede af applikationens struktur. En af de mest effektive tilgange i denne sammenhæng er brugen af router-first arkitektur. Denne tilgang prioriterer routing som grundlæggende element i applikationens opbygning, hvilket gør det muligt at skabe en dynamisk og fleksibel struktur, der let kan tilpasses og udvides efter behov.

Router-first arkitektur adskiller sig fra traditionelle metoder ved at begynde med ruten og dens struktur, før man implementerer applikationens logik. Dette betyder, at man definerer de forskellige ruter (eller navigationspunkter) i applikationen og bygger funktionaliteten omkring disse ruter. Denne tilgang skaber en mere modulær og skalerbar løsning, hvor komponenter kan tilføjes eller fjernes uden at påvirke de grundlæggende navigations- og routingstrukturer.

Når man starter med router-first tilgang, er det vigtigt at definere de forskellige brugerroller og de nødvendige adgange til applikationen. Det er afgørende at forstå, hvilke funktioner der skal være tilgængelige for de forskellige brugergrupper, og hvordan disse funktioner bedst implementeres gennem ruter. Ved at tage højde for brugerens behov og de data, der er nødvendige for at interagere med applikationen, kan man sikre, at systemet er både effektivt og brugervenligt.

En af de primære fordele ved router-first arkitektur er, at den understøtter lazy loading. Lazy loading betyder, at kun de nødvendige komponenter og moduler bliver hentet, når de faktisk er nødvendige, hvilket forbedrer applikationens performance betydeligt. Ved at implementere lazy loading kan man sikre, at applikationen ikke bliver overbelastet med unødvendige ressourcer, hvilket gør den mere responsiv og hurtigere at oprette forbindelse til.

En anden vigtig fordel er muligheden for at definere et "walking skeleton" af applikationen. Dette betyder, at man bygger en grundlæggende version af applikationen med de vigtigste ruter og funktioner, som senere kan udvides med yderligere funktionalitet. Dette er en ideel tilgang, når man arbejder med store og komplekse enterprise-applikationer, da det giver et klart billede af, hvordan applikationen vil udvikle sig, og gør det lettere at justere og tilpasse efterhånden som kravene ændrer sig.

Når man arbejder med Angular i en router-first arkitektur, er det også nødvendigt at forstå, hvordan Angular's moduler fungerer. Modulerne giver en organiseret måde at strukturere applikationens funktionalitet på, og de gør det muligt at definere specifikke ruter og komponenter for hvert modul. For at sikre, at applikationen er både effektiv og let at vedligeholde, bør man opdele applikationen i flere moduler, som hver især håndterer en bestemt del af funktionaliteten.

En vigtig overvejelse, når man arbejder med router-first arkitektur, er at definere en klar og konsistent rute-struktur, der giver mening for både udviklere og brugere. Dette inkluderer at bruge beskrivende og intuitive rutenavne, der gør det nemt at navigere i applikationen. Det er også vigtigt at sikre, at ruterne er lette at vedligeholde og udvide, så applikationen kan skaleres over tid.

En veludført router-first arkitektur i Angular giver ikke kun en effektiv og fleksibel struktur, men sikrer også, at applikationen kan tilpasses og skaleres, som virksomheden vokser. Det er en essentiel tilgang, når man arbejder med store og komplekse applikationer, hvor kravene kan ændre sig hurtigt, og hvor der er behov for en høj grad af fleksibilitet og performance.

Hvordan designes autentificering og autorisation i Angular-applikationer?

For at implementere en simpel loginmekanisme i en Angular-applikation, skal vi først definere login-funktionen i en komponent som HomeComponent. Denne komponent vil benytte AuthService, som er ansvarlig for at autentificere brugeren. Vi starter med at oprette en funktion, der kalder AuthService's login-metode og bruger den til at logge ind med faste loginoplysninger, som f.eks. en testbrugers e-mail og adgangskode.

I HomeComponent kan vi implementere login-funktionen som følgende:

typescript
import { AuthService } from '../auth/auth.service'
export class HomeComponent implements OnInit { constructor(private authService: AuthService) {} ngOnInit(): void {} login() {
this.authService.login('[email protected]', '12345678');
} }

I template-filen for HomeComponent skal vi opdatere knappen eller linket, så den i stedet for at navigere via routerLink, kalder login()-funktionen direkte. Dette kan gøres ved at erstatte den tidligere HTML med en simpel knap, der udfører login-processen.

Efter login er det vigtigt at navigere brugeren til en specifik rute, f.eks. /manager, når login er succesfuldt. For at gøre dette, skal vi lytte til authStatus$ og currentUser$ observables, der er eksponeret af AuthService. Hvis authStatus$.isAuthenticated er true og currentUser$._id ikke er en tom streng, har vi et gyldigt login. Vi kan anvende RxJS's combineLatest operator til at lytte til begge observables samtidig, og bruge filter operatoren til at reagere, når login er succesfuldt, og derefter navigere til /manager ruten.

Opdateringen af login-funktionen kunne se således ud:

typescript
import { Router } from '@angular/router';
export class HomeComponent implements OnInit {
constructor( private authService: AuthService, private router: Router ) {} login() { this.authService.login('[email protected]', '12345678'); combineLatest([ this.authService.authStatus$, this.authService.currentUser$ ]) .pipe( filter(([authStatus, user]) => authStatus.isAuthenticated && user?._id !== ''), tap(([authStatus, user]) => {
this.router.navigate(['/manager']);
}) ) .
subscribe(); } }

Det er vigtigt at bemærke, at vi abonnerer på combineLatest-operatoren til sidst. Dette er nødvendigt for at aktivere de observable strømme. Uden denne abonnememt vil login-handlingen forblive inaktiv, medmindre en anden komponent abonnerer på strømmen. Derfor skal en strøm kun aktiveres én gang.

Når login-processen er implementeret, skal vi teste funktionaliteten. Vi kan verificere, at JWT-tokenet bliver oprettet og gemt i localStorage via Chrome DevTools, hvor vi kan se applikationens lokale lagring. Hvis login er succesfuldt, kan vi kontrollere, at et token, f.eks. med navnet jwt, er til stede. Det er også vigtigt at bemærke advarsler om ikke at bruge InMemoryAuthService eller fake-jwt-sign i produktionskode.

Når vi tester login-systemet og genindlæser applikationen, vil vi opdage, at brugeren forbliver logget ind, fordi tokenet bliver gemt i localStorage. Dette betyder, at vi har en grundlæggende login-løsning, men vi skal også implementere en logout-funktion for at fuldende autentificeringsarbejdsgangen.

Logout-funktionen er forbundet med en knap i applikationens værktøjslinje. Når denne knap aktiveres, skal brugeren logges ud og navigeres tilbage til hjemmesiden. For at implementere logout, skal vi opdatere logout-komponenten til at kalde logout()-metoden i AuthService og derefter rydde localStorage:

typescript
import { AuthService } from '../../auth/auth.service';
import { Router } from '@angular/router';
export class LogoutComponent implements OnInit {
constructor(private router: Router, private authService: AuthService) {} ngOnInit() {
this.authService.logout(true);
this.router.navigate(['/']); } }

Når logout er gennemført, kan vi sikre os, at localStorage er blevet renset, og brugeren bliver logget ud korrekt. Dette afslutter den grundlæggende login- og logout-workflow i applikationen.

En anden vigtig komponent i autentificering er at overveje udløbsstatusen af JWT-tokenet. Det ville være en dårlig brugeroplevelse at kræve, at brugeren logger ind hver gang, de besøger applikationen, men det ville også være uhensigtsmæssigt at lade brugeren være logget ind for evigt. JWT-tokenet har en udløbsdato, som kan variere afhængigt af sikkerhedsbehovene for applikationen.

For at håndtere sessionens udløb korrekt skal vi implementere en funktion i AuthService, der kontrollerer, om tokenet er udløbet. Hvis tokenet er udløbet, bør vi automatisk navigere brugeren til login-siden, hvilket giver en mere glidende brugeroplevelse.

Funktionen til at tjekke, om tokenet er udløbet, kan implementeres som følger:

typescript
protected hasExpiredToken(): boolean { const jwt = this.getToken(); if (jwt) {
const payload = decode(jwt) as any;
return Date.now() >= payload.exp * 1000; } return true; }

I AuthService skal vi derefter opdatere konstruktøren til at tjekke status på tokenet ved opstart:

typescript
constructor() {
super(); if (this.hasExpiredToken()) { this.logout(true); } else { this.authStatus$.next(this.getAuthStatusFromToken()); } }

Dette sikrer, at hvis tokenet er udløbet, bliver brugeren logget ud, og tokenet fjernes fra localStorage. Hvis tokenet stadig er gyldigt, opdateres autentificeringsstatussen.

For en smidig genoptagelse af en brugers session, kan vi definere et observable, der opdaterer den aktuelle bruger, hvis autentificeringen er gyldig. Dette sikrer, at brugeren ikke mister sin session, når applikationen genindlæses.