Angular

Lab 8: Routing (Bonus)

arrow_back Alle labs
timer 15 minutter
star Bonus Modul
Mål: Tilføj routing til appen, så brugerne kan navigere mellem CO₂ Beregneren og en Historik side — uden at hele siden reloades.

Hvad er routing?

Lige nu viser vores app kun én ting: CO₂ beregneren. Men de fleste apps har flere sider. Tænk på et website med en forside, en "Om os" side og en kontaktside. Normalt ville browseren hente en helt ny HTML-side hver gang du klikker på et link.

Routing i Angular fungerer anderledes. I stedet for at hente en ny side fra serveren, bytter Angular bare den synlige komponent ud. URL'en i adresselinjen ændrer sig stadig (så du kan dele links og bruge browserens frem/tilbage knapper), men selve siden reloader aldrig. Det føles derfor lynhurtigt.

Angulars Router kigger på URL'en og bestemmer hvilken komponent der skal vises. Du definerer reglerne i en routes konfiguration: "Når URL'en er /, vis Calculator. Når den er /historik, vis History."

Trin for trin

looks_one Opret History component

Først opretter vi den side brugerne skal kunne navigere til. Kør Angular CLI:

ng generate component history/history

Åbn den nye history/history.ts og giv den et simpelt indhold:

// history/history.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-history',
  template: `
    <div class="history-page">
      <h2>📋 Historik</h2>
      <p>Historik — kommer snart</p>
    </div>
  `,
  styles: `
    .history-page {
      text-align: center;
      padding: 2rem;
    }
  `
})
export class History {}

looks_two Konfigurer routing

Nu skal vi fortælle Angular hvilken komponent der hører til hvilken URL. Åbn app.config.ts og tilføj provideRouter(routes):

// app.config.ts
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { provideRouter } from '@angular/router';
import { Routes } from '@angular/router';
import { Calculator } from './calculator/calculator';
import { History } from './history/history';

const routes: Routes = [
  { path: '', component: Calculator },
  { path: 'historik', component: History },
  { path: '**', redirectTo: '' }
];

export const appConfig: ApplicationConfig = {
  providers: [
    provideZonelessChangeDetection(),
    provideHttpClient(withFetch()),
    provideRouter(routes),
  ]
};

Her definerer vi tre routes:

looks_3 Tilføj router-outlet

Routeren ved nu hvilken komponent der skal vises, men den mangler et sted at placere den. Det gør vi med <router-outlet />. Tænk på det som en pladsholder: "Her skal den aktive sides komponent vises."

Åbn app.ts og erstat den direkte <app-calculator /> med <router-outlet />:

// app.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { Header } from './layout/header/header';
import { Footer } from './layout/footer/footer';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, Header, Footer],
  template: `
    <app-header />
    <router-outlet />
    <app-footer />
  `
})
export class App {}

Bemærk at <app-header /> og <app-footer /> forbliver på plads. De vises altid. Det er kun indholdet mellem dem der skifter ud baseret på URL'en.

looks_4 Tilføj navigation i header

Nu skal brugerne kunne klikke sig mellem siderne. I header komponenten tilføjer vi links med Angulars routerLink direktiv. Det virker som et almindeligt <a> tag, men i stedet for at lave et helt page reload, beder det blot routeren om at skifte komponent.

Åbn layout/header/header.ts og tilføj imports:

// layout/header/header.ts
import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';

@Component({
  selector: 'app-header',
  imports: [RouterLink, RouterLinkActive],
  templateUrl: './header.html',
  styleUrl: './header.css',
})
export class Header {}

Opdater header.html med navigation:

<!-- layout/header/header.html -->
<div class="site-header">
  <div class="header-inner">
    <a class="logo" routerLink="/">
      <img src="logo.png" alt="Logo" width="150" height="55" />
    </a>
    <nav>
      <a routerLink="/"
         routerLinkActive="active"
         [routerLinkActiveOptions]="{ exact: true }">
        CO2 Beregner
      </a>
      <a routerLink="/historik"
         routerLinkActive="active">
        Historik
      </a>
    </nav>
  </div>
</div>

Her bruger vi tre ting fra Angular Router:

looks_5 Test navigation

Start dev-serveren og prøv at klikke mellem de to links i headeren:

ng serve
  1. Klik på "Historik" i headeren — du ser historik siden
  2. Klik på "CO2 Beregner" — du er tilbage til beregneren
  3. Observer at URL'en i adresselinjen ændrer sig (/ og /historik)
  4. Observer at siden aldrig reloader — skiftet sker øjeblikkeligt
  5. Prøv browserens frem og tilbage knapper — de virker som forventet
  6. Observer at det aktive link får en active klasse (inspicér med DevTools)

Bonus: Lazy loading

Lige nu importeres History komponenten direkte i app.config.ts. Det betyder at dens kode indgår i det JavaScript der hentes ved første sideload, selvom brugeren måske aldrig besøger historik siden.

Med lazy loading kan vi fortælle Angular: "Hent først History komponentens kode når brugeren faktisk navigerer til /historik." Det gør vi ved at erstatte component med loadComponent:

// app.config.ts — lazy loading version
const routes: Routes = [
  { path: '', component: Calculator },
  {
    path: 'historik',
    loadComponent: () =>
      import('./history/history').then(m => m.History)
  },
  { path: '**', redirectTo: '' }
];

Nu kan du fjerne import { History } fra toppen af filen — den importeres dynamisk i stedet.

Test det:

  1. Åbn DevTools → Network tab
  2. Reload siden på forsiden (/)
  3. Klik på "Historik"
  4. Observer at en ny JavaScript-fil (en "chunk") først hentes nu. Det er History komponentens kode der lazy loades!
lightbulb
Hvorfor er lazy loading smart? I en stor app med mange sider sparer du brugerne for at downloade kode de aldrig bruger. Forsiden loader hurtigere, og resten hentes on demand. Angular splitter automatisk koden i separate filer (chunks) baseret på dine loadComponent routes.

Hvad lærte vi?

Hints

Hint: Komplet app.config.ts med routing
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { provideRouter } from '@angular/router';
import { Routes } from '@angular/router';
import { Calculator } from './calculator/calculator';

const routes: Routes = [
  { path: '', component: Calculator },
  {
    path: 'historik',
    loadComponent: () =>
      import('./history/history').then(m => m.History)
  },
  { path: '**', redirectTo: '' }
];

export const appConfig: ApplicationConfig = {
  providers: [
    provideZonelessChangeDetection(),
    provideHttpClient(withFetch()),
    provideRouter(routes),
  ]
};
Hint: Komplet app.ts med router-outlet
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { Header } from './layout/header/header';
import { Footer } from './layout/footer/footer';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, Header, Footer],
  template: `
    <app-header />
    <router-outlet />
    <app-footer />
  `
})
export class App {}
Hint: Komplet header.ts med RouterLink
import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';

@Component({
  selector: 'app-header',
  imports: [RouterLink, RouterLinkActive],
  template: `
    <div class="site-header">
      <div class="header-inner">
        <a class="logo" routerLink="/">
          <img src="logo.png" alt="Logo" width="150" height="55" />
        </a>
        <nav>
          <a routerLink="/"
             routerLinkActive="active"
             [routerLinkActiveOptions]="{ exact: true }">
            CO2 Beregner
          </a>
          <a routerLink="/historik"
             routerLinkActive="active">
            Historik
          </a>
        </nav>
      </div>
    </div>
  `,
  styleUrl: './header.css',
})
export class Header {}

Dokumentation