ES2015 introduserte et nytt modulsystem som kombinerer det beste fra eksisterende tilnærminger uten å binde seg til en spesifikk implementasjon. Ecma TC39-komiteen, ansvarlig for språkutviklingen, ønsket en enkel og elegant syntaks som tar med seg fordeler fra både CommonJS og AMD, samtidig som den støtter asynkron lasting, statisk analyse og håndtering av sirkulære avhengigheter. Denne modulstrukturen er essensiell i Angular, hvor nesten alt er organisert i moduler som må importeres for å kunne brukes.

Med ES2015-moduler eksporterer man funksjoner, klasser eller konstanter ved hjelp av nøkkelordet export. Dette kan gjøres som navngitte eksporter, der flere elementer eksporteres individuelt, og importeres ved å referere til dem med navn i import-setningen. For eksempel:

js
// races-service.js export function bet(race, pony) { /* ... */ } export function start(race) { /* ... */ }

I en annen fil kan man importere disse funksjonene slik:

js
import { bet, start } from './races-service';

Man kan også gi importerte elementer aliaser for bedre lesbarhet:

js
import { start as startRace } from './races-service';

Hvis man ønsker å importere alt fra en modul, kan man bruke en joker (*) sammen med et alias. Dette gir klarhet i koden ved at man alltid refererer til importene via aliaset:

js
import * as racesService from './races-service'; racesService.bet(race, pony); racesService.start(race);

For moduler som kun eksporterer én verdi, funksjon eller klasse, kan man benytte default-eksport. Dette gjør importen enklere og uten krøllparenteser:

js
// pony.js export default class Pony {} // races-service.js import Pony from './pony';

Det er også mulig å kombinere en default-eksport med flere navngitte eksporter i samme modul, men bare én default kan finnes per modul.

I Angular blir slike moduler og import-/eksport-mekanismer brukt gjennomgående. Hver komponent eller tjeneste er vanligvis en egen klasse i sin egen fil, eksportert og importert etter behov, noe som gir en klar struktur og modularitet i applikasjonen.

JavaScript i seg selv er et dynamisk typet språk, noe som gir fleksibilitet, men også utfordringer. Uten statiske typer kan det være vanskelig å vite hvilke verdier som forventes, spesielt når man arbeider med ukjente API-er eller kode skrevet av andre. Dette kan føre til vedlikeholdsproblemer, vanskelig feilsøking og økt risiko for bugs som ikke oppdages før kjøretid. Verktøy og IDEer har begrensede muligheter til å hjelpe uten typeinformasjon.

For å møte disse utfordringene, har Google og Angular-miljøet valgt å satse på TypeScript, et superset av JavaScript som tilbyr et valgfritt, statisk typesystem. TypeScript ble valgt fordi det allerede hadde en etablert brukerbase og aktiv utvikling, og samarbeidet mellom Microsoft og Google har gjort at Angular nå offisielt anbefales skrevet i TypeScript. Denne tilnærmingen kombinerer det beste fra begge verdener: fleksibiliteten i JavaScript med tryggheten og vedlikeholdbarheten som statiske typer gir.

TypeScript gjør det lettere å forstå koden, gir bedre støtte i utviklingsverktøy og reduserer feil ved kompileringstid. Det letter også refaktorering og videreutvikling av store kodebaser, noe som er kritisk for omfattende applikasjoner som Angular-prosjekter.

Enda viktigere er det å forstå at modulsystemet ikke bare handler om syntaks eller organisering, men om å skape en robust struktur som muliggjør effektiv utvikling, testing og vedlikehold. Å beherske import- og eksportmekanismer, samt å utnytte typeinformasjon via TypeScript, er grunnleggende for å skrive moderne, skalerbar og pålitelig frontend-kode.

Det er også verdt å merke seg at mens dynamisk typing åpner for stor fleksibilitet, kan det skape skjulte fallgruver i større prosjekter. Statisk typing og modulsystemer støtter ikke bare maskinen i å sjekke koden, men hjelper også utvikleren i å navigere og forstå kodebasen over tid. Dette er spesielt viktig i et økosystem som Angular, hvor kompleksiteten raskt kan vokse.

Hvordan lage og bruke egne Angular-piper i applikasjoner

I Angular kan vi bruke innebygde piper for å formatere og manipulere data i maler. Men en viktig og kraftig funksjonalitet som Angular tilbyr, er muligheten til å lage egne tilpassede piper. Dette er nyttig når man ønsker spesifikke dataformater som ikke er tilgjengelige via standardpiper.

For å lage en egen pipe, begynner vi med å opprette en ny klasse som implementerer grensesnittet PipeTransform. Dette grensesnittet pålegger oss å definere en metode kalt transform(), som vil inneholde logikken for transformasjon av dataene. La oss se på et konkret eksempel på hvordan dette kan gjøres.

Eksempel på en enkel pipe

Tenk deg at du ønsker å lage en pipe som viser hvor mye tid som har gått siden en gitt dato. Dette kan være nyttig når man for eksempel viser tid for hendelser som skjedde for en stund siden. For å gjøre dette kan vi bruke biblioteket date-fns, som tilbyr funksjoner for å manipulere datoer. Først må du installere dette biblioteket via NPM:

bash
npm install date-fns

Deretter kan vi implementere pipe-en ved å bruke funksjonene parseISO og formatDistanceToNowStrict fra date-fns. Her er et eksempel på hvordan man kan gjøre dette:

typescript
import { Pipe, PipeTransform } from '@angular/core'; import { formatDistanceToNowStrict, parseISO } from 'date-fns'; @Pipe({ name: 'fromNow' }) export class FromNowPipe implements PipeTransform { transform(value: string, ..._args: Array<any>): string { const date = parseISO(value); return formatDistanceToNowStrict(date, { addSuffix: true }); } }

I dette eksemplet tar vi inn en dato som en streng, parser den til et Date-objekt ved hjelp av parseISO, og bruker deretter formatDistanceToNowStrict for å beregne og formatere hvor mye tid som har gått fra denne datoen. Resultatet kan for eksempel være noe som "2 dager siden" eller "om 3 timer".

Bruke pipe-en i komponenten

Når pipe-en er definert, må vi registrere den i komponenten der den skal brukes. Dette gjøres ved å inkludere pipe-en i imports-arrayen i komponentens dekoratør:

typescript
@Component({ selector: 'ns-race', template: 'Racet startet {{ race().startInstant | fromNow }}', imports: [FromNowPipe] }) export class Race { protected readonly race = signal({ startInstant: '2023-02-10T10:00:00.000Z' }); }

Her bruker vi pipe-en i malens interpolasjon for å vise hvordan lang tid som har gått siden racet startet.

Fordeler med å lage egne piper

Å lage sine egne piper gir deg stor fleksibilitet til å håndtere spesifikke datatransformasjoner som ikke nødvendigvis er dekket av standardpiper i Angular. Det kan være alt fra datovisualisering, spesifikke tallformater til tekstbehandling. En annen fordel med egne piper er at de kan kapsle inn logikk som kan gjenbrukes på tvers av applikasjonen, noe som gjør koden mer modulær og lettere å vedlikeholde.

Abstraksjon og gjenbruk av kode

Når vi lager en pipe, kan vi abstrahere kompleks logikk som tidligere måtte være skrevet direkte i malene, og i stedet håndtere dette i den dedikerte pipe-klassen. Dette fører til at malene blir renere og mer lesbare. For eksempel, om du ønsker å vise en formatert dato flere steder i applikasjonen, kan du lage en pipe som gjør dette én gang, og deretter bruke den i flere komponenter.

Opprettelse av flere piper

Det er også mulig å lage mer komplekse piper som tar flere argumenter. La oss si at vi ønsker å lage en pipe som tar både en dato og et formatvalg som argumenter. Da kan vi tilpasse transformasjonsmetoden vår for å håndtere disse forskjellige parametrene og gi fleksible løsninger for malene.

Bruke piper med eksterne biblioteker

Ofte trenger vi å bruke eksterne biblioteker for mer spesifikke oppgaver, som i eksemplet ovenfor med date-fns. Dette er en vanlig praksis i Angular-utvikling, og Angular gjør det lett å integrere slike biblioteker ved å bruke piper.

Dependency Injection i Angular

Når du jobber med Angular, er en annen sentral teknologi verdt å forstå: Dependency Injection (DI). Dette er et designmønster som Angular bruker for å håndtere objekter og tjenester i applikasjonen. DI gjør at Angular kan håndtere opprettelsen og livssyklusen til objektene, i stedet for at utvikleren selv må gjøre det. Dette gir en rekke fordeler, som enklere testing og muligheten til å bytte ut implementeringer av tjenester etter behov.

I Angular kan du injisere tjenester i komponenter, direkte ved hjelp av constructor-injeksjon eller via den nyere inject-funksjonen som ble introdusert i Angular 14. Et eksempel på injeksjon via constructor kan se slik ut:

typescript
export class RaceService { constructor(private loggingService: LoggingService) {} }

Ved å bruke DI kan vi gjøre komponentene våre mer modulære, lettere å teste og mer fleksible i håndteringen av ulike tjenester som kan endre seg over tid. Det gjør det også enklere å bytte ut en implementering (for eksempel for utviklings- eller produksjonsmiljøer).


Det er viktig å merke seg at piper og DI er bare to av mange verktøy i Angular som gjør det mulig å lage effektive og vedlikeholdbare applikasjoner. Når man utvikler i Angular, bør man ha et klart syn på hvordan man kan bruke disse konseptene for å redusere kompleksiteten i applikasjonens arkitektur og samtidig gjøre koden mer modulær og lett gjenbrukbar. Å bruke egendefinerte piper og forstå DI-prinsippene gjør det mulig å bygge fleksible og skalerbare applikasjoner på en mer effektiv måte.

Hvordan konfigurerer og navigerer du i Angular-applikasjoner?

I Angular er ruter en uunnværlig mekanisme for navigering mellom forskjellige komponenter i applikasjonen. Rute-konfigurasjonen er en liste med objekter som beskriver hvordan hver rute fungerer og hvilken komponent som skal vises ved navigering til en bestemt URL. For eksempel kan en enkel rute-konfigurasjon se slik ut:

typescript
import { Routes } from '@angular/router'; import { Home } from './home/home'; import { Races } from './races/races'; export const routes: Routes = [ { path: '', component: Home }, { path: 'races', component: Races } ];

Dette definerer to ruter: én for hjem-siden og én for rasesiden. Deretter må vi sørge for at Angular-ruteren er tilgjengelig i applikasjonen, og initialisere den med den riktige konfigurasjonen. Dette gjøres på følgende måte:

typescript
import { bootstrapApplication } from '@angular/platform-browser'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; bootstrapApplication(App, { providers: [provideRouter(routes)] }).catch(err => console.error(err));

Ruter i Angular består vanligvis av to viktige egenskaper: path, som definerer hvilken URL som skal utløse navigeringen, og component, som spesifiserer hvilken komponent som skal vises. Når en rute aktiveres, er det viktig å forstå hvor komponenten skal plasseres i applikasjonens mal. For å gjøre dette, brukes en Angular-direktiv kalt RouterOutlet, som fungerer som en plassholder for den gjeldende komponentens mal.

html
<header></header> <router-outlet></router-outlet> <footer></footer>

I dette eksemplet vil alt utenom komponenten (header og footer) forbli uendret, mens den relevante komponenten vises på plassen for <router-outlet>. Det er viktig å merke seg at RouterOutlet må importeres i komponentens dekoratør for at den skal være tilgjengelig i malen.

Når det gjelder navigasjon, er det flere måter å bevege seg mellom komponentene på. Man kan manuelt skrive inn URL-en i nettleseren, men dette fører til at hele applikasjonen lastes på nytt, noe som vi vanligvis ønsker å unngå. Den mest effektive måten å navigere på i Angular er å bruke RouterLink-direktivet i malene. For eksempel, for å navigere til hjemme-siden fra en annen side, kan du bruke følgende kode:

html
<a routerLink="/">Home</a>

Ved kjøring vil RouterLink beregne riktig URL, som i dette tilfellet er roten av applikasjonen (/). Et viktig poeng er at dersom du ikke inkluderer skråstreken ("/"), vil URL-en beregnes relativt til den nåværende sti, noe som kan være nyttig i enkelte situasjoner.

I tillegg kan RouterLinkActive-direktivet brukes til å automatisk legge til en CSS-klasse når lenken peker til den aktive ruten, noe som gjør det enkelt å markere den valgte siden i et navigasjonsmeny.

En annen måte å navigere på er programmatisk via Angulars Router-tjeneste og dens navigate()-metode. Dette er nyttig når du for eksempel vil omdirigere brukeren til en annen rute etter en handling, som for eksempel etter å ha lagret data:

typescript
export class Races { private readonly router = inject(Router); protected saveAndMoveBackToHome(): void { // ... lagre logikk ... this.router.navigate(['']); } }

En annen interessant funksjon i Angular er muligheten for dynamiske URL-er. Dette er nyttig for å lage URL-er som inkluderer variabler, som når man for eksempel skal vise detaljsider for bestemte elementer, som i et dyrebutikk-eksempel med ponnyer:

typescript
export const routes: Routes = [ { path: '', component: Home }, { path: 'races', component: Races }, { path: 'races/:raceId/ponies/:ponyId', component: Pony } ];

For å lage en lenke til en slik side kan du bruke routerLink som peker på den dynamiske ruten:

html
<a routerLink="/races/1/ponies/123">Se ponny</a>

Komponenten som er knyttet til denne ruten, må deretter få tilgang til parameterne i URL-en. Dette kan gjøres ved å injisere ActivatedRoute-tjenesten i komponenten. ActivatedRoute tilbyr en snapshot-egenskap, som gir tilgang til parametrene i URL-en på tidspunktet da ruten lastes:

typescript
export class Pony { protected readonly ponyModel: Signal; constructor() { const route = inject(ActivatedRoute); const id = route.snapshot.paramMap.get('ponyId')!; const ponyService = inject(PonyService); this.ponyModel = toSignal(ponyService.get(id)); } }

For mer dynamisk håndtering kan man bruke observables i stedet for snapshot, spesielt hvis URL-en endres uten at komponenten lastes på nytt. Dette gir mulighet for å reagere på endringer i URL-en mens komponenten fortsatt er aktiv.

typescript
export class PonyReusable { protected readonly ponyModel: Signal; constructor() { const route = inject(ActivatedRoute); const ponyService = inject(PonyService); this.ponyModel = toSignal( route.paramMap.pipe( map((params: ParamMap) => params.get('ponyId')!), switchMap(id => ponyService.get(id)) ) ); } }

Med denne metoden kan applikasjonen oppdatere komponenten i sanntid, uten å måtte laste den på nytt når URL-en endres. Dette er spesielt nyttig i applikasjoner der brukeren navigerer mellom flere dynamiske data, for eksempel i en bilde- eller produktgalleri.

Ruter i Angular gir dermed en kraftig mekanisme for å lage navigasjon uten å måtte laste hele siden på nytt, og den kan utvides til å håndtere mer komplekse scenarier som dynamiske ruter og parametre. Det er viktig å forstå hvordan man konfigurerer ruter, bruker RouterLink for navigasjon, og håndterer dynamiske URL-er for å bygge en sømløs brukeropplevelse.

Endtext