Når vi ser på de moderne frameworks som Angular og React.js, er det tydeligt, at hver af dem bringer deres egen tilgang til at håndtere applikationens tilstand (state management) og arkitektur. Begge tilgange er avancerede og fleksible, men de er designet med forskellige målsætninger og fordele i tankerne.

NgRx er en populær state management løsning for Angular, som anvender et reaktivt mønster baseret på observables fra RxJS. Denne tilgang introducerer en abstraktionslag over allerede komplekse værktøjer som RxJS, hvilket giver udviklere mulighed for at håndtere applikationens tilstand på en meget struktureret og skalerbar måde. En af de primære årsager til at bruge NgRx er, når man arbejder med applikationer, der kræver håndtering af flere inputstrømme - for eksempel når du har tre eller flere datakilder, der konstant skal opdateres og synkroniseres. I sådanne scenarier gør den ekstra kompleksitet ved at håndtere events NgRx til en værdifuld løsning. På den anden side, for mange applikationer, der kun har to inputstrømme - REST APIs og brugerinput - er det ikke nødvendigt at implementere en så tung løsning som NgRx. Her kan enklere værktøjer som RxJS og BehaviorSubject være tilstrækkelige til at opnå den nødvendige funktionalitet uden at tilføre unødvendig kompleksitet.

NgRx kan være et godt valg i scenarier som Progressive Web Apps (PWAs), der er designet til at være offline-first, hvor man skal håndtere komplicerede state-ændringer og sørge for vedvarende data gennem sessioner. Her giver NgRx et robust grundlag for at håndtere den type kompleksitet, der er nødvendig for at sikre en god brugeroplevelse i offline-situationer. For applikationer, der ikke kræver så omfattende state management, er NgRx dog ikke nødvendigvis den bedste løsning.

En alternativ løsning, som også stammer fra NgRx-økosystemet, er NgRx Component Store. Dette bibliotek sigter mod at forenkle state management på komponentniveau, hvilket giver en meget mere fokuseret og effektiv tilgang, når man kun behøver at håndtere tilstanden for en enkelt komponent eller en lille samling af komponenter. NgRx Component Store giver udviklere mulighed for at opbygge isolerede og testbare tilstandshåndteringssystemer, der automatisk rydder op, når komponenterne ikke længere er i brug. Denne tilgang gør det muligt at have flere instanser af en komponent store i en applikation, hvilket giver stor fleksibilitet i opbygningen af komponentbaserede systemer.

På den anden side har React.js en helt anden arkitektur. I modsætning til Angular, der benytter sig af en mere traditionel MVC-arkitektur, anvender React Flux-mønstret, som er strengt baseret på en ensrettet datastream. I starten af React’s livscyklus var det nødvendigt at passere data mellem komponenter gennem hele komponenttræet. Dette kunne hurtigt blive uhåndterligt, især når komponenterne skulle modtage data fra fjerne dele af applikationen. Senere blev React-Redux introduceret, hvilket gjorde det muligt for komponenter at læse og skrive direkte til en global state store uden at skulle navigere op og ned i komponenttræet. Redux har gjort det lettere at styre applikationens tilstand, hvilket gør React til et særligt godt valg til komplekse webapplikationer, der har brug for en enkel og klar struktur til state management.

React’s community har, som Angular’s, konstant udviklet og forbedret patterns og praktikker, hvilket gør det til et fleksibelt og dynamisk framework, der kan tilpasses mange forskellige behov. For dem, der er på jagt efter enkelhed, kan Vue.js være et godt alternativ. Vue.js er kendt for sin intuitive og letforståelige tilgang til både komponentbaseret udvikling og state management, hvilket gør det til en attraktiv løsning for både små og store projekter.

Når man ser fremad, står Angular overfor flere udfordringer, især i forhold til performance og feature-releases. Selvom Angular har haft en fast release-cyklus med opdateringer hver sjette måned, har denne hastighed medført, at mange funktioner er blevet lanceret i en ufuldstændig eller eksperimentel tilstand. For eksempel blev Angular Elements annonceret som en løsning til at bygge genbrugelige webkomponenter, men teamet har ikke været i stand til at afslutte denne funktion på grund af de tekniske udfordringer ved at minimere frameworkets størrelse. Derudover har der været problemer med performance-regressioner i nyere versioner af Angular, hvilket viser, at hurtige releases kan føre til uforudsete problemer i produktionen.

Angular 17 indeholder nogle spændende nyheder som signals, som tilbyder en forbedring af reaktiviteten ved at erstatte Zone.js med native JavaScript-funktioner. Dette giver en mere præcis kontrol over ændringer i DOM'en, hvilket kan resultere i hurtigere opdateringer og bedre ydeevne. Med disse nye features er Angular i gang med at tage skridt mod at forbedre Time-to-Interactive (TTI) og bruge server-side rendering (SSR) med ikke-destruktiv hydrering, som gør det muligt for serveren at sende en DOM til klienten, som derefter kan opdatere sig selv uden at genindlæse hele siden.

Endelig er der en tendens mod at bruge modern JavaScript, herunder ES2022-funktioner, til at bygge mere reaktive og interaktive webapplikationer uden at være afhængig af store frameworks som Angular. Dette gør det muligt at bygge hurtigere, mere effektive applikationer med færre eksterne afhængigheder, hvilket kan være en stor fordel, især for mindre projekter eller applikationer, der ikke har brug for den fulde kompleksitet af et framework som Angular.

Hvordan modernisering af byggesystemer og testautomatisering kan optimere udviklingsarbejdet

I moderne softwareudvikling er effektivitet og hastighed essentielle faktorer. For at kunne levere apps hurtigt og uden at gå på kompromis med kvaliteten, er det nødvendigt at benytte de nyeste værktøjer og teknologier. En af de vigtigste fordele ved at vælge de rette værktøjer er at minimere udviklingstiden og maksimere produktiviteten. Dette kan opnås ved at implementere avancerede build-systemer som Nx og esbuild, samt ved at anvende moderne testautomatiseringsværktøjer som Cypress og Vitest.

Nx er et next-generation build-system, der tilbyder første klasses support for monorepo-arkitektur og stærke integrationer. Med Nx kan du opdele din applikationskode i biblioteker og udnytte build-caching til kun at genbygge de dele af appen, der er nødvendige. Dette betyder, at en lille ændring ikke nødvendigvis kræver en komplet genbygning af appen, men snarere en kort build på 30 sekunder, hvor kun de tests, der er påvirket af ændringen, bliver kørt. Den store fordel ved dette system er, at cachen kan deles eksternt mellem servere og udviklingsmaskiner, hvilket gør det muligt for teams at arbejde hurtigere og mere effektivt. Derudover tilbyder Nx en meningsfuld arkitektur, der er særligt værdifuld for store teams og virksomheder, og systemet automatiserer opdatering af afhængigheder, hvilket sparer tid og minimerer vedligeholdelsesopgaver.

En af de primære udfordringer i moderne webprojekter er at optimere byggetiderne. Her kommer esbuild ind i billedet. Esbuild er en ekstremt hurtig bundler, der er 40 gange hurtigere end webpack 5, som Angular oprindeligt benyttede til at pakke sin SPA (Single Page Application). Med Angular 17 er esbuild-baserede ES-moduler blevet standard for byggesystemet. Det betyder, at byggetiderne kan reduceres drastisk, hvilket har stor betydning for udviklerens produktivitet. Denne opgradering gør det muligt at bruge værktøjer som Vite, der giver næste generation af frontend-værktøjer til hurtigere opbygning og optimering af applikationer.

Testautomatisering er et andet område, der kræver opmærksomhed. Traditionsrige værktøjer som Karma og Jasmine er ved at vise tegn på alderdom. Karma blev aldrig designet med henblik på at køre headless unit tests, og det har gjort det vanskeligt at bruge i moderne udviklingsmiljøer. Den oprindelige ende-til-ende (e2e) testværktøj til Angular, Protractor, er allerede blevet depreceret og erstattet af Cypress. Cypress har hurtigt vundet popularitet og er blevet den foretrukne løsning til automatisering af tests. Det er et fantastisk værktøj til både e2e tests og komponenttests, og det gør det lettere at skrive nye tests. Selvom Cypress er et godt valg, er der også andre alternativer som Vitest og Jest, der kan være passende afhængigt af det specifikke behov.

Jest er en anden mulighed for unit testing, og det tilbyder et hurtigt og effektivt testmiljø. Det er dog ikke blevet designet til at understøtte ES-moduler, hvilket kan føre til kompatibilitetsproblemer, når man bruger CommonJS og ES-moduler sammen i eksisterende applikationer. Dette kan føre til vanskeligheder med at opgradere ældre projekter. Vitest, som er drevet af Vite, er et nyere og hurtigere alternativ til Jest, og det fungerer godt sammen med den esbuild-baserede byggekonfiguration.

Det er vigtigt at forstå, at selv om disse værktøjer og teknologier kan forbedre hastigheden og effektiviteten af udviklingsarbejdet, er de kun en del af det større billede. At have en solid plan og en god proces er lige så væsentligt. Agile metoder som Kanban og Scrum spiller en afgørende rolle i at organisere arbejdet og sikre, at udviklingsteamet arbejder effektivt og fokuseret på de rigtige opgaver. Kanban-systemer, som dem der kan oprettes i GitHub Projects, giver et klart og transparent billede af, hvad der skal arbejdes på, hvad der er i gang, og hvad der er færdigt. Et dynamisk informationsradiator, som en Kanban-tavle, giver teammedlemmer og interessenter konstant opdaterede informationer om projektets status, hvilket er med til at fremme samarbejde og beslutningstagning.

En god praksis for ethvert udviklingsteam er at opretholde en live backlog, hvor de kommende opgaver er prioriteret og tydeligt synlige. At bruge GitHub Projects som en Kanban-tavle er en enkel og effektiv metode til at holde styr på opgaver og fremskridt, samtidig med at den letter kommunikationen i teamet.

Selvom teknologier som Nx og esbuild kan forbedre ydeevnen og reducere byggetider, bør udviklere ikke glemme vigtigheden af at vælge de rette metoder til at organisere arbejdet og sikre, at alle teammedlemmer er synkroniserede. En klar struktur og værktøjer til at administrere projektet vil ikke kun forbedre udviklingshastigheden, men også reducere risikoen for fejl og misforståelser i teamet.

Hvordan Designes en Effektiv Token-baseret Auth Workflow?

Når vi arbejder med moderne webapplikationer og API'er, er det vigtigt at forstå, hvordan et token-baseret autentificeringssystem fungerer. Et velfungerende system baseret på tokens skal være stateless, hvilket betyder, at der ikke er et koncept om en udløbende session. Brugerne skal kunne interagere med systemet på flere enheder og i flere faner uden at skulle logge ind igen, hver gang de skifter enhed eller browser.

Et JSON Web Token (JWT) er et centralt element i sådanne systemer. JWT'er er en industristandard (RFC 7519) og giver mulighed for distribueret, pålidelig og digitalt signeret autentifikation. Når en bruger er autentificeret via en loginformular, modtager de et token (et krypteret claims-billet), som kan bruges til at foretage fremtidige anmodninger til serveren uden at skulle gennemgå en ny autentifikation. Serveren kan validere tokenet og bekræfte, at brugeren er den, de påstår at være, uden at gemme sessionen på serveren. Dette gør systemet stateless og let at skalere.

JWT'ens livscyklus er relativt simpel: først modtager brugeren et token efter at have logget ind, og når de laver anmodninger til serveren, medfølger tokenet i headeren af hver HTTP-anmodning. Serveren verifikere tokenet og tillader brugeren at få adgang til de nødvendige data, hvis det er gyldigt. En vigtig pointe her er, at JWT'er ikke kan tilbagekaldes individuelt, da de er distribueret, men de kan udløbe efter en forudbestemt periode, hvilket giver et lag af sikkerhed.

Designe en god autorisationsworkflow betyder at tage hensyn til brugerens rolle. Afhængigt af denne rolle skal bestemte UI-elementer eller navigationsruter kun være synlige for brugere med passende adgang. Det er vigtigt at forstå, at alle klient-side baserede rollebaserede navigeringer kun er en bekvemmelighed og ikke skal bruges som sikkerhedsfunktion. Rigtig sikkerhed skal implementeres på server-siden, hvor hver anmodning bliver validert med token og adgangsretter.

Desuden er det vigtigt at sikre, at al kommunikation mellem klient og server foregår over HTTPS, så data som brugernavne og adgangskoder er beskyttet mod at blive opsnappet under transmission. Når det kommer til lagring af følsomme data som personligt identificerbare oplysninger (PII), bør disse krypteres ved hjælp af en sikker to-vejs krypteringsalgoritme. Dette betyder, at hvis databasen skulle blive kompromitteret, vil den stjålne data være ubrugelig uden adgang til krypteringsnøglerne.

Når der opstår behov for at nulstille en brugers adgangskode, skal dette ske sikkert. Reset-sider bør genereres server-side, da der er flere måder en bruger kan interagere med dem på, fx via en e-mail- eller notifikationslink. For at forhindre uautoriseret adgang bør reset-token'et være tidsbegrænset og kun kunne bruges én gang.

Et centralt element i ethvert autentificeringssystem er at beskytte data både under transport og i hvile. Alle data, der sendes mellem klienter, systemer og databaser, bør krypteres med Transport Layer Security (TLS). Når data er gemt i en database, bør følsomme oplysninger som adgangskoder opbevares ved hjælp af en sikker én-vejs hashing-algoritme. Dette beskytter data, selv hvis en hacker skulle få adgang til databasen.

For at sikre et solidt og sikkert system er det nødvendigt at tage en lagdelt tilgang til sikkerhed. Hvis én lag skulle blive kompromitteret, er det nødvendigt for en angriber at bryde igennem flere lag for at opnå væsentlig skade. Dette er grunden til, at store databrud ofte skyldes manglende korrekt implementering af sikkerhed under transport eller i hvile, som kan være for dyrt at implementere på en kontinuerlig basis.

I forbindelse med JWT-baseret autentifikation er der tre væsentlige komponenter, der spiller en vigtig rolle i arbejdsflowet. På klientsiden håndteres loginoplysninger og sikre UI-interaktioner. Serveren validerer hver anmodning og sikrer, at den indeholder den nødvendige autorisation. Endelig genererer og validerer autentificeringstjenesten krypterede tokens og kontrollerer, at anmodningen er gyldig, uden at den behøver at holde styr på sessionen.

Når man opbygger et autentificeringssystem, er det vigtigt at huske på, at god sikkerhed ikke kun handler om at sikre data, men også om at sikre den proces, som dataene gennemgår – fra autentificering til tokenvalidering og autorisation.

Hvordan man implementerer master/detalje visning med paginerede datatabeller i Angular

For at implementere en effektiv master/detalje visning i en Angular-applikation, er det essentielt at håndtere datalastning, navigation og visning på en måde, der understøtter brugervenlighed og funktionalitet. I denne implementering anvender vi en matrixtabel, hvor data vises i paginerede segmenter, samtidig med at vi integrerer en detaljevisning for individuelle elementer.

En af de første ting, man bør forstå, er, hvordan Angular-routeren håndterer ruter og outlets. I visse scenarier kan routeren miste forbindelsen til specifikke outlets, hvis ruterne ikke er korrekt konfigureret. For eksempel, hvis master outlet er eksplicit defineret som i følgende kode:

javascript
['../users', { outlets: { master: [''], detail: ['user', { userId: row.id }] } }]

Dette vil ikke blive korrekt parse af routeren og vil fejle i at indlæse. Hvis i stedet outlet er defineret som master: [], vil det fungere. Dette skyldes den måde, hvorpå mønstergenkendelse sker for tomme ruter. Selvom dette giver mening i frameworkets kode, kan det være kontraintuitivt for udviklere, der arbejder med API'erne.

Når vi har forstået, hvordan routing fungerer, og har implementeret den nødvendige logik for resolve guards i vores ViewUserComponent, kan vi begynde at debugge. Ved at bruge Chrome DevTools kan vi se, at dataen indlæses korrekt, og this.currentUser bliver tildelt uden yderligere boilerplate-kode i ngOnInit-funktionen. Dette viser den klare fordel ved at bruge en resolve guard.

Når master/detalje-visningen er på plads, er det tid til at fokusere på implementeringen af en pagineret datatabel i master outlet. Her introducerer vi UserTableComponent, som vil indeholde en MatTableDataSource-ejendom, der fungerer som datakilde. For at kunne hente brugerdata effektivt skal vi kunne arbejde med pagination, som muliggør visning af data i håndterbare portioner.

I første omgang er det nødvendigt at oprette en IUsers interface, der beskriver datastrukturen for de paginerede data:

typescript
export interface IUsers { data: IUser[]; total: number; }

Dernæst opdateres UserService med en ny funktion, getUsers, der tager højde for pagination, søgning og sortering. Funktionen returnerer et Observable, der indlæser dataene fra serveren baseret på de givne parametre:

typescript
getUsers(pageSize: number, searchText = '', pagesToSkip = 0, sortColumn = '', sortDirection: '' | 'asc' | 'desc' = 'asc'): Observable<IUsers> {
const recordsToSkip = pageSize * pagesToSkip; if (sortColumn) { sortColumn = sortDirection === 'desc' ? `-${sortColumn}` : sortColumn; } return this.httpClient.get<IUsers>(`${environment.baseUrl}/v2/users`, { params: { filter: searchText, skip: recordsToSkip.toString(), limit: pageSize.toString(), sortKey: sortColumn, }, }); }

Det er vigtigt at bemærke, at sorteringsretningen indikeres ved hjælp af asc for stigende og desc for faldende. Når vi sorterer i stigende orden, sender vi blot kolonnenavnet som en parameter til serveren. For at sortere i faldende orden præfikser vi kolonnenavnet med et minus.

Efter at have defineret funktionaliteten for at hente paginerede data, kan vi nu fokusere på at konfigurere UserTableComponent med pagination, sortering og filtrering. I komponenten definieres nødvendige egenskaber som items$, der håndterer observable-strømmen af data, som skal vises i tabellen. displayedColumns er en computed signal, der dynamisk definerer, hvilke kolonner der skal vises i tabellen, og demoViewDetailsColumn bruges til at styre visningen af ekstra kolonner.

For at sikre, at alle brugergrænseflade-interaktioner er glatte og intuitivt håndteret, anvender vi ngAfterViewInit til at abonnere på ændringer i paginering, sortering og søgning. Hver gang en bruger ændrer på en af disse indstillinger, opdateres visningen, og dataene indlæses på ny med de nye parametre.

Når der er behov for at navigere til detaljevisningen for en bestemt bruger, benyttes routeren til at vise den valgte brugers detaljer i en navngiven outlet:

typescript
showDetail(userId: string) { this.router.navigate(
['../users', { outlets: { detail: ['user', { userId: userId }] } }],
{
skipLocationChange: true, relativeTo: this.activatedRoute, } ); }

Denne metode gør det muligt for brugeren at se detaljer om en valgt bruger, samtidig med at masterlisten forbliver synlig i baggrunden.

Endelig, når vi arbejder med komponenter som UserTableComponent, er det afgørende at forstå den samlede datainteraktion og hvordan asynkrone data indlæses og håndteres i Angular. Selv om vi kan implementere pagination, sortering og filtrering effektivt, er det vigtigt at sikre, at der ikke opstår ydeevneproblemer eller utilsigtede sideeffekter, som kan påvirke brugeroplevelsen negativt.

Hvordan implementere CI/CD for automatiserede tests i softwareudvikling

Når man arbejder med softwareudvikling, er det afgørende at sikre, at koden, der bliver sendt til produktion, er stabil og fungerer som forventet. En vigtig praksis, der hjælper med at opnå dette, er implementeringen af Continuous Integration (CI) og Continuous Deployment (CD). Dette giver udviklerteams mulighed for at køre automatiserede tests på deres kode for at sikre, at den ikke introducerer fejl, før den bliver sendt til produktion.

Når man opbygger et system med automatiserede tests, som for eksempel med Cypress til end-to-end (e2e) tests i Angular-applikationer, er det vigtigt at bruge teststrategier, der er både robuste og vedligeholdelsesvenlige. Et af de grundlæggende principper i dette setup er brugen af test-id'er, som gør det muligt at finde HTML-elementer på en pålidelig måde, selvom de måtte ændre placering på siden. I eksemplet med "LocalCast Weather" applikationen bruges en test som følger:

typescript
describe('LocalCast Weather', () => {
beforeEach(() => { cy.visit('/') }) it('has the correct title', () => { cy.byTestId('title').should('have.text', 'LocalCast Weather') }) })

I denne test ses det, hvordan cy.byTestId("title") bruges til at finde et element i DOM'en, der har en data-testid="title"-attribut. Dette gør det muligt at sikre, at testene forbliver stabile, selv når elementet ændrer sin placering på siden. Det er en enkel, men effektiv metode, der kan bruges til at opbygge pålidelige og letlæselige tests. Når vi arbejder med komplekse applikationer, er det ofte nødvendigt at anvende yderligere strukturer som Page Objects for at holde koden organiseret og let at vedligeholde.

Når tests er sat op, er det næste skridt at sørge for, at de kører automatisk, hver gang koden bliver ændret. Her kommer Continuous Integration (CI) ind i billedet. CI hjælper med at køre de automatiserede tests på servere, før koden bliver skubbet til produktion. En af de mest populære CI-løsninger er CircleCI, som giver udviklere mulighed for at opsætte en pipeline, hvor hver ændring af koden bliver testet for at sikre, at det ikke bryder noget i applikationen. CircleCI understøtter både enkle opsætninger med gratis tier og mere avancerede setups, som kan tilpasses til specifikke behov.

En grundlæggende CircleCI-konfiguration ser således ud:

yaml
version: 2.1
orbs: cypress: cypress-io/cypress@3 jobs: run_tests: docker: - image: cimg/node:lts-browsers steps: - checkout - run: npm install - run: npx ng build --configuration production - run: npx ng test --watch=false
- run: npx ng run local-weather-app:cypress-run
workflows: version: 2 build-and-test: jobs: - run_tests

Denne opsætning sikrer, at tests bliver kørt, hver gang koden pushes til et repository. Når du har konfigureret CircleCI, kan du oprette en pull request og få CI-pipelinen til at køre tests automatisk. Hvis testsene ikke består, kan du stoppe uønskede ændringer i at blive deployeret til produktion, og på den måde sikre, at kun stabil kode bliver sendt videre.

En vigtig komponent i CI/CD-arbejdsgangen er GitHub flow. Dette workflow hjælper med at sikre, at kodeændringer bliver godkendt og testet, før de bliver integreret i hovedbranchen. GitHub flow understøtter en enkel, branch-baseret proces, hvor udviklere arbejder på deres egne grene, laver commits og sender pull requests for at få feedback og til sidst få koden flettet ind i den primære kodebase.

Trin i GitHub flow inkluderer:

  1. Branching – Opret en ny gren for hver ny funktion eller fejlrettelse.

  2. Commits – Lav flere commits på din gren.

  3. Pull Request – Send pull request til teamet, som indeholder den nyeste kode, og få feedback.

  4. Review og Diskutér – Gennemgå koden sammen med teamet, lav nødvendige ændringer.

  5. Deploy – Test koden på et staging-miljø før den går i produktion.

  6. Merge – Flet ændringerne ind i hovedgrenen, når alt er godkendt.

For at sikre, at GitHub flow fungerer korrekt, bør du konfigurere beskyttelsesregler for din hovedgren. Det betyder, at du kun kan foretage ændringer gennem pull requests, og at disse pull requests skal være godkendt og have bestået alle nødvendige checks, som f.eks. CircleCI-tests, før de kan merges.

Når du arbejder med CI/CD, er det vigtigt at forstå, at det ikke kun handler om at køre tests, men også om at skabe en proces, hvor kvalitetskontrol er en integreret del af udviklingscyklussen. Dette hjælper med at undgå de problemer, der kan opstå, når fejl først opdages i produktionen, og giver udviklingsteams mulighed for hurtigt at identificere og rette problemer, før de påvirker slutbrugeren.