From 6f668fce2cb534290798052db33339f111bb9d12 Mon Sep 17 00:00:00 2001 From: Danny Koppenhagen Date: Thu, 21 May 2026 19:43:19 +0200 Subject: [PATCH 01/14] wip --- material/guards/README.md | 450 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 material/guards/README.md diff --git a/material/guards/README.md b/material/guards/README.md new file mode 100644 index 00000000..63aa4b99 --- /dev/null +++ b/material/guards/README.md @@ -0,0 +1,450 @@ +--- +title: 'Guards: Routen absichern' +published: 2026-05-17 +lastModified: 2026-05-17 +--- + +Normalerweise kann jede Route einer Angular-Anwendung uneingeschränkt betreten und wieder verlassen werden. +In komplexeren Anwendungen gibt es allerdings Bereiche, die nur unter bestimmten Umständen aufgerufen werden sollen – z. B. abhängig von Authentifizierung, Berechtigungen oder dem Zustand der Anwendung. +Der Angular-Router bietet dafür ein Feature an, mit dem wir Routen absichern können: *Route Guards*. + +## Inhalt + +[[toc]] + +## Grundlagen zu Guards + +Ein Guard ist eine Funktion, die entscheidet, ob ein Navigationsschritt ausgeführt werden darf oder nicht. +Routen können von Guards „beschützt" werden, sodass stets der Guard durchlaufen werden muss, bevor die Navigation tatsächlich ausgeführt wird. +Auf diese Weise können Guards die Nutzerführung in der Anwendung steuern. + +Die Entscheidung, ob die Navigation durchgeführt wird, wird durch den Rückgabewert der Guard-Funktion ausgedrückt. +Dafür sind diese Varianten möglich: + +- `true`: Die Navigation wird **ausgeführt**. +- `false`: Die Navigation wird **abgebrochen**. +- Typ `UrlTree` oder `RedirectCommand`: Die Navigation wird **abgebrochen**, und es wird zu einer anderen Route navigiert. + +Dieser Rückgabewert kann synchron aus der Funktion zurückgegeben werden, oder er kann in ein Observable oder in eine Promise verpackt werden. +Damit ist es möglich, asynchrone Operationen im Guard zu verarbeiten: Zum Beispiel kann ein HTTP-Request durchgeführt werden, dessen Antwort entscheidet, ob navigiert werden darf. + +> ⚠️ **Wichtig:** Guards steuern die Nutzerführung, aber sie sind kein Sicherheitsfeature! +> Der gesamte kompilierte Code der Anwendung kann vom Browser jederzeit heruntergeladen werden. +> Die Sicherheit der Daten muss immer vom Backend ausgehen: Nur wenn die bedienende Person authentifiziert und autorisiert ist, darf der Server die Daten an den Client senden. +> Guards helfen uns, abhängig von diesen Zuständen die Nutzerführung zu steuern. + +## Varianten von Guards + +Wir unterscheiden verschiedene Arten von Guards, mit denen wir unsere Routen absichern können: + +| Variante | entscheidet, ob … | +|----------|-------------------| +| `CanActivate` | eine Route betreten werden darf | +| `CanActivateChild` | Kind-Routen einer Route betreten werden dürfen | +| `CanDeactivate` | eine Route verlassen werden darf (wegnavigieren) | +| `CanMatch` | eine Route bei der Auswertung berücksichtigt wird | + +Ein Guard wird immer als Funktion entwickelt, die einer bestimmten Signatur folgt. + +## Guards verwenden + +Haben wir eine Guard-Funktion entwickelt, können wir sie auf unsere Routen anwenden: +Guards werden als Eigenschaft einer Routendefinition angegeben und wirken als eine Art Middleware. +Die verwendete Eigenschaft deutet immer auf die Art des Guards hin, z. B. `canActivate`. +Die Guards werden als Array aufgelistet, denn es können auch mehrere Guards für eine Route festgelegt werden. +In diesem Fall werden sie der Reihe nach durchlaufen. + +```typescript +const routes: Routes = [ + { + path: 'foo', + component: FooComponent, + canActivate: [myActivateGuard] + }, + { + path: 'bar', + component: BarComponent, + canDeactivate: [leaveGuard] + } +]; +``` + +Rufen wir z. B. die Route mit dem Pfad `foo` auf, wird zunächst die Guard-Funktion `myActivateGuard` ausgeführt. +Liefert sie den Wert `true` zurück, wird die Route geladen, andernfalls wird die Navigation abgebrochen. +Gibt der Guard einen `UrlTree` oder ein `RedirectCommand` zurück, so wird eine neue Navigation zu der neuen Route gestartet. + +## Guards implementieren + +Ein Guard wird als Funktion implementiert, die einer bestimmten Typisierung folgt. +Durch den Typ wird die Signatur der Funktion festgelegt, sodass der Router sicher mit dem Guard arbeiten kann. +Je nach Guard-Variante nimmt die Funktion verschiedene Argumente entgegen. + +Zum Anlegen eines Guards können wir die Angular CLI verwenden: + +```bash +ng generate guard foo --guardType CanActivate +ng generate guard bar --guardType CanMatch +``` + +### CanActivate: Darf die Route aktiviert werden? + +Mit einem `CanActivate`-Guard können wir prüfen, ob eine bestimmte Route betreten werden darf. +Wir verwenden den Typ `CanActivateFn`, um die Guard-Funktion zu typisieren. +Sie erhält zwei optionale Argumente: + +- `ActivatedRouteSnapshot`: Informationen zur angefragten Route, z. B. Routenparameter. +- `RouterStateSnapshot`: Der gesamte Zustand des Routers. + +Wollen wir im Guard auf Services zugreifen, z. B. um die Entscheidung abhängig von einem zentralen Zustand zu machen, verwenden wir die Funktion `inject()`. + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn } from '@angular/router'; + +export const myActivateGuard: CanActivateFn = + (route, state) => { + // Routenparameter lesen + const foo = route.paramMap.get('foo'); + + // Service injizieren + const authService = inject(AuthService); + + // Entscheidung treffen (true / false) + return authService.isAuthenticated(); + }; +``` + +### Umleitung mit UrlTree + +Wenn eine Navigation nicht erlaubt ist, kann es sinnvoll sein, stattdessen zu einer anderen Route umzuleiten. +Dafür können wir aus dem Guard ein Objekt vom Typ `UrlTree` zurückgeben. +Dieser `UrlTree` ist eine von Angular geparste Route und gibt dem Router die Anweisung, zu dieser Route zu navigieren. +Er lässt sich mithilfe des Routers erzeugen: Die Methode `Router.parseUrl()` wandelt eine URL in einen `UrlTree` um: + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; + +export const myActivateGuard: CanActivateFn = () => { + const authService = inject(AuthService); + + if (authService.isAuthenticated()) { + return true; + } + + const router = inject(Router); + return router.parseUrl('/login'); +}; +``` + +Alternativ können wir einen `UrlTree` auch mit der Methode `Router.createUrlTree()` erzeugen. +Hier übergeben wir ein Array von Routensegmenten. +Außerdem können wir im zweiten Argument ein Objekt mit weiteren Optionen notieren, z. B. den Bezugspunkt für eine relative URL: + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; + +export const myActivateGuard: CanActivateFn = () => { + // ... + const router = inject(Router); + return router.createUrlTree(['login']); +}; +``` + +### Umleitung mit RedirectCommand + +Seit Angular 18.1 gibt es eine weitere Möglichkeit, eine Umleitung aus einem Guard heraus auszulösen: das `RedirectCommand`. +Im Gegensatz zum `UrlTree` können wir damit zusätzliche Navigationsoptionen angeben, z. B. `replaceUrl` oder `skipLocationChange`: + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn, Router, RedirectCommand } from '@angular/router'; + +export const myActivateGuard: CanActivateFn = () => { + const authService = inject(AuthService); + + if (authService.isAuthenticated()) { + return true; + } + + const router = inject(Router); + const urlTree = router.parseUrl('/login'); + return new RedirectCommand(urlTree, { replaceUrl: true }); +}; +``` + +### Warum UrlTree statt Router.navigate()? + +Theoretisch könnten wir aus dem Guard heraus auch direkt die Methode `Router.navigate()` aufrufen, um zu einer anderen Route zu wechseln. +Praktisch hat der `UrlTree` (bzw. das `RedirectCommand`) allerdings einen entscheidenden Vorteil, wenn mehrere Guards aktiv sind, die asynchron arbeiten. +In einer solchen Konstellation ist nie klar, welcher der Guards wann eine Antwort liefert und damit über das Ziel der Navigation entscheidet. + +Verwenden wir einen `UrlTree` als Rückgabewert, verhält sich der Router deterministisch: +Die Guards, die näher an der Wurzel der Routenhierarchie aktiv sind, haben eine höhere Priorität als die Guards, die tiefer im Baum platziert sind. +Der Router kümmert sich um die Priorisierung und Weiterleitung. + +Verwende deshalb bitte immer einen `UrlTree` oder ein `RedirectCommand`, um aus einem Guard heraus zu einer anderen Route zu navigieren. + +### CanDeactivate: Darf die aktive Route verlassen werden? + +Mit einem `CanDeactivate`-Guard können wir prüfen, ob die gerade aktive Route verlassen werden darf. +Die Guard-Funktion erhält als erstes Argument eine Referenz auf die Komponente, die durch die Navigation verlassen wird. +Der Typ dieser Komponente wird mit dem generischen Typparameter `T` im Typ `CanDeactivateFn` angegeben. + +Die Funktion erhält die gesamte Instanz der Komponente als Argument: +Wir können also Daten aus der Komponente abfragen und die Entscheidung abhängig vom Zustand machen. +Das ist z. B. sinnvoll, um zu prüfen, ob Änderungen in einem Formular vorgenommen wurden, die nicht verworfen werden sollen. + +```typescript +import { CanDeactivateFn } from '@angular/router'; +import { MyComponent } from './my.component'; + +export const leaveGuard: CanDeactivateFn = + (component) => { + return !component.hasUnsavedChanges; + }; +``` + +Wenn wir den Guard in der Route verwenden, wird die Navigation weg von der Komponente `MyComponent` nur ausgeführt, wenn das Property `hasUnsavedChanges` in der Komponente den Wert `false` hat. +Diese Eigenschaft ist selbst definiert und muss natürlich innerhalb der Komponente gesteuert werden. + +> **Tipp: Guard wiederverwenden** +> +> Durch den generischen Typparameter ist der gezeigte `CanDeactivate`-Guard spezifisch für eine bestimmte Komponente. +> Um das zu vermeiden, empfehlen wir, ein Interface anzulegen, das die benötigte Schnittstelle der Komponente vorgibt, z. B. das Property `hasUnsavedChanges`. +> Alle Komponenten, die diese Schnittstelle für den Guard anbieten, müssen das Interface implementieren. +> Der Guard verwendet in seinem Typparameter dann ebenfalls das Interface. So kann der Guard mit verschiedenen Komponenten arbeiten und ist nicht nur für eine einzelne Komponente verwendbar. + +```typescript +export interface HasUnsavedChanges { + hasUnsavedChanges: boolean; +} + +export const leaveGuard: CanDeactivateFn = + (component) => { + if (component.hasUnsavedChanges) { + return confirm('Du hast ungespeicherte Änderungen. Möchtest du die Seite wirklich verlassen?'); + } + return true; + }; +``` + +### CanActivateChild: Dürfen Kind-Routen betreten werden? + +Ein Guard mit dem Typ `CanActivateChildFn` entscheidet, ob zu den Kindern der betreffenden Route navigiert werden darf. +Dieser Guard wird für *alle* Kind-Routen ausgeführt, die mit `children` definiert sind. +Das ist besonders nützlich, um eine ganze Gruppe von Routen mit einer einzigen Prüfung abzusichern. + +```typescript +import { inject } from '@angular/core'; +import { CanActivateChildFn } from '@angular/router'; + +export const adminChildGuard: CanActivateChildFn = + (childRoute, state) => { + const authService = inject(AuthService); + return authService.hasRole('admin'); + }; +``` + +In der Routenkonfiguration wird der Guard auf der Elternroute notiert: + +```typescript +const routes: Routes = [ + { + path: 'users', + canActivateChild: [adminChildGuard], + children: [ + { path: 'list', component: UserListComponent }, + { path: 'detail/:id', component: UserDetailComponent }, + ], + }, +]; +``` + +### CanMatch: Wird die Route berücksichtigt? + +Ein Guard mit dem Typ `CanMatchFn` entscheidet, ob eine Route bei der Auswertung berücksichtigt wird. +Navigieren wir zu einer URL, werden alle Routen von oben nach unten abgearbeitet. +Für jede Route wird geprüft, ob sie zur angeforderten URL passt – und die erste passende Route wird geladen. +Mit `CanMatch`-Guards können wir entscheiden, ob eine Route dabei überhaupt ausgewertet oder übersprungen wird. + +Die Guard-Funktion erhält zwei Argumente: das gesamte Routen-Objekt und die angeforderte URL in Form eines Arrays von URL-Segmenten. +Gibt die Funktion `false` zurück, wird die betreffende Route bei der Auswertung übersprungen – der Router versucht dann, eine andere passende Route zu finden. +Auch dieser Guard kann einen `UrlTree` zurückgeben, um eine neue Navigation anzustoßen. + +```typescript +import { inject } from '@angular/core'; +import { CanMatchFn } from '@angular/router'; + +export const myMatchGuard: CanMatchFn = + (route, segments) => { + return inject(AuthService).isAuthenticated(); + }; +``` + +Da wir auf diese Weise bestimmte Routen von der Auswertung ausschließen können, eignet sich `CanMatch` für ein besonderes Szenario: +Wir können mehrere Varianten einer Route anbieten, die auf verschiedene Komponenten zeigen. +Entscheidet der Guard, dass die erste Route nicht berücksichtigt wird, wird die zweite verwendet. + +```typescript +const routes: Routes = [ + { + path: 'myfeature', + component: FeatureComponent, + canMatch: [myMatchGuard] + }, + { + path: 'myfeature', + component: AnonymousFeatureComponent + } +]; +``` + +Ist also im `AuthService` das Property `isAuthenticated` auf `true` gesetzt, wird die erste Route mit der `FeatureComponent` geladen. +Steht der Zustand auf `false`, wird die `AnonymousFeatureComponent` angezeigt. + +Dieses Muster eignet sich hervorragend für Feature Flags, A/B-Testing oder bedingte Routenauswahl. + +## Guards inline definieren + +In allen bisherigen Beispielen haben wir die Funktionen separat abgelegt, sodass wir sie in den Routen referenzieren können. +Wollen wir einen Guard nur einmalig nutzen, können wir die Funktion auch direkt in der Route definieren: + +```typescript +const routes: Routes = [ + { + path: 'bar', + component: MyComponent, + canDeactivate: [ + (comp: MyComponent) => !comp.hasUnsavedChanges, + ] + }, + { + path: 'secret', + component: SecretComponent, + canActivate: [() => inject(AuthService).isAuthenticated()] + } +]; +``` + +## Praxisbeispiel: Admin-Bereich absichern + +Schauen wir uns ein vollständiges Beispiel an. +Wir wollen den Administrationsbereich einer Anwendung absichern, sodass er nur für angemeldete Personen zugänglich ist. +Die Information zum Authentifizierungsstatus erhalten wir aus einem `AuthService`. + +### Guard implementieren + +Wir erstellen einen `CanActivate`-Guard, der den Authentifizierungsstatus prüft. +Ist die Person angemeldet, erlauben wir die Navigation. +Falls nicht, leiten wir zur Startseite um und zeigen eine Meldung an: + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; + +export const authGuard: CanActivateFn = () => { + const authService = inject(AuthService); + const router = inject(Router); + + if (authService.isAuthenticated()) { + return true; + } + + alert('Bitte melde dich an, um den Admin-Bereich zu betreten.'); + return router.parseUrl('/home'); +}; +``` + +### Guard in der Route verwenden + +In der Routenkonfiguration fügen wir die Eigenschaft `canActivate` hinzu und geben die Guard-Funktion an. +Damit wird der Guard ausgeführt, bevor die Route geladen wird: + +```typescript +const routes: Routes = [ + { + path: 'admin', + loadChildren: () => import('./admin/admin.routes'), + canActivate: [authGuard] + }, + // ... +]; +``` + +Wenn wir die Basisroute für das Lazy Loading mit dem Guard sichern, sind auch alle darunter folgenden Routen abgedeckt. + +### Alternative: Asynchroner Guard mit Observable + +In einer produktiven Anwendung erhalten wir den Status der Authentifizierung möglicherweise nicht synchron. +Unser `AuthService` bietet die Information zusätzlich über ein Observable an. +In diesem Fall können wir den Guard asynchron implementieren: + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; +import { map, take } from 'rxjs'; + +export const authGuard: CanActivateFn = () => { + const authService = inject(AuthService); + const router = inject(Router); + + return authService.isAuthenticated$.pipe( + take(1), + map(isAuthenticated => { + if (isAuthenticated) { + return true; + } + return router.parseUrl('/login'); + }) + ); +}; +``` + +Es ist wichtig, dass wir die Länge des Datenstroms mithilfe von `take(1)` begrenzen: Wir sind nur an einem einzigen Wert interessiert, nicht an allen danach folgenden. + +## Diskussion: Den richtigen Guard-Typ wählen + +Die Wahl des Guard-Typs hängt davon ab, *wann* die Prüfung stattfinden soll: + +- **`CanActivate`**: Die Route wird zunächst aufgelöst (und bei Lazy Loading heruntergeladen). Bevor die Komponente aktiviert wird, entscheidet der Guard. +- **`CanMatch`**: Die Prüfung findet statt, *bevor* die Route überhaupt aufgelöst wird. Bei Lazy Loading wird das Bundle nur heruntergeladen, wenn der Guard es erlaubt. +- **`CanActivateChild`**: Sichert alle Kind-Routen einer Elternroute mit einer einzigen Prüfung ab. +- **`CanDeactivate`**: Prüft, ob die aktuelle Route verlassen werden darf. + +Für die Absicherung von Lazy-Loading-Routen empfehlen wir `CanMatch`, da so das Bundle nur bei Bedarf geladen wird. +Für einfache Authentifizierungsprüfungen auf einzelnen Routen ist `CanActivate` die gängigste Wahl. + +## Mehrere Guards kombinieren + +Guards werden als Array angegeben und in der definierten Reihenfolge ausgeführt. +Alle Guards müssen `true` zurückgeben, damit die Navigation stattfindet. +Gibt einer der Guards `false` oder einen `UrlTree` zurück, wird die Navigation abgebrochen bzw. umgeleitet. + +```typescript +const routes: Routes = [ + { + path: 'admin', + component: AdminComponent, + canActivate: [authGuard, adminRoleGuard] + }, +]; +``` + +In diesem Beispiel muss die Person sowohl authentifiziert sein als auch die Admin-Rolle besitzen, um die Route zu betreten. + +## Zusammenfassung + +- Mit Guards können Routen abgesichert werden, sodass die Navigation unter bestimmten Umständen abgebrochen wird. +- Ein Guard ist eine Funktion, die entscheidet, ob die Navigation ausgeführt werden darf. +- Es gibt vier Arten von Guards: + - `CanActivate` – beim Aufruf einer Route + - `CanDeactivate` – beim Verlassen einer Route + - `CanActivateChild` – beim Aufruf einer Kind-Route + - `CanMatch` – beim Auswerten der Route +- Der Rückgabewert eines Guards ist ein `boolean`, ein `UrlTree` oder ein `RedirectCommand`. Der Rückgabewert kann auch asynchron von einem Observable oder einer Promise geliefert werden. +- Gibt der Guard `true` zurück, wird die Navigation ausgeführt, bei `false` wird sie abgebrochen. +- Gibt der Guard einen `UrlTree` oder ein `RedirectCommand` zurück, wird eine neue Navigation zur enthaltenen Route gestartet. +- Guards werden als Eigenschaft einer Routendefinition notiert und wirken dann auf diese Route. +- Guards steuern die Nutzerführung, aber sie sind kein Sicherheitsfeature. Die Absicherung muss immer serverseitig erfolgen. From b37cd3da234e66b48ae4a0b8d5246575ed604b2d Mon Sep 17 00:00:00 2001 From: Danny Koppenhagen Date: Fri, 22 May 2026 11:51:23 +0200 Subject: [PATCH 02/14] docs(material/guards): update publication and modification dates - Update published date from 2026-05-17 to 2026-05-23 - Update lastModified date from 2026-05-17 to 2026-05-23 --- material/guards/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/material/guards/README.md b/material/guards/README.md index 63aa4b99..d23d4ed1 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -1,7 +1,7 @@ --- title: 'Guards: Routen absichern' -published: 2026-05-17 -lastModified: 2026-05-17 +published: 2026-05-23 +lastModified: 2026-05-23 --- Normalerweise kann jede Route einer Angular-Anwendung uneingeschränkt betreten und wieder verlassen werden. From 07ec6501e3c573a6347ee53d298dccc2f6b364c2 Mon Sep 17 00:00:00 2001 From: Danny Koppenhagen Date: Fri, 22 May 2026 14:20:04 +0200 Subject: [PATCH 03/14] Apply suggestions from code review Co-authored-by: Ferdinand Malcher --- material/guards/README.md | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/material/guards/README.md b/material/guards/README.md index d23d4ed1..e0c4daf1 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -55,18 +55,18 @@ Die Guards werden als Array aufgelistet, denn es können auch mehrere Guards fü In diesem Fall werden sie der Reihe nach durchlaufen. ```typescript -const routes: Routes = [ - { - path: 'foo', - component: FooComponent, - canActivate: [myActivateGuard] - }, - { - path: 'bar', - component: BarComponent, - canDeactivate: [leaveGuard] - } -]; +export const routes: Routes = [ + { + path: 'foo', + component: FooPage, + canActivate: [myActivateGuard] + }, + { + path: 'bar', + component: BarPage + canDeactivate: [leaveGuard] + } +]; ``` Rufen wir z. B. die Route mit dem Pfad `foo` auf, wird zunächst die Guard-Funktion `myActivateGuard` ausgeführt. @@ -114,7 +114,7 @@ export const myActivateGuard: CanActivateFn = }; ``` -### Umleitung mit UrlTree +### Umleitung mit `UrlTree` Wenn eine Navigation nicht erlaubt ist, kann es sinnvoll sein, stattdessen zu einer anderen Route umzuleiten. Dafür können wir aus dem Guard ein Objekt vom Typ `UrlTree` zurückgeben. @@ -139,7 +139,7 @@ export const myActivateGuard: CanActivateFn = () => { Alternativ können wir einen `UrlTree` auch mit der Methode `Router.createUrlTree()` erzeugen. Hier übergeben wir ein Array von Routensegmenten. -Außerdem können wir im zweiten Argument ein Objekt mit weiteren Optionen notieren, z. B. den Bezugspunkt für eine relative URL: +Außerdem können wir im zweiten Argument ein Objekt mit weiteren Optionen notieren, z. B. den Bezugspunkt für eine relative URL. ```typescript import { inject } from '@angular/core'; @@ -174,7 +174,7 @@ export const myActivateGuard: CanActivateFn = () => { }; ``` -### Warum UrlTree statt Router.navigate()? +### Warum `UrlTree` statt `Router.navigate()`? Theoretisch könnten wir aus dem Guard heraus auch direkt die Methode `Router.navigate()` aufrufen, um zu einer anderen Route zu wechseln. Praktisch hat der `UrlTree` (bzw. das `RedirectCommand`) allerdings einen entscheidenden Vorteil, wenn mehrere Guards aktiv sind, die asynchron arbeiten. @@ -379,7 +379,9 @@ Wenn wir die Basisroute für das Lazy Loading mit dem Guard sichern, sind auch a In einer produktiven Anwendung erhalten wir den Status der Authentifizierung möglicherweise nicht synchron. Unser `AuthService` bietet die Information zusätzlich über ein Observable an. -In diesem Fall können wir den Guard asynchron implementieren: +In diesem Fall können wir den Guard asynchron implementieren. +Er gibt dann ein Observable oder eine Promise zurück. +Der Router wartet auf die asynchrone Operation und entscheidet dann mit dem Ergebnis, ob und wie die Navigation ausgeführt wird. ```typescript import { inject } from '@angular/core'; @@ -404,7 +406,7 @@ export const authGuard: CanActivateFn = () => { Es ist wichtig, dass wir die Länge des Datenstroms mithilfe von `take(1)` begrenzen: Wir sind nur an einem einzigen Wert interessiert, nicht an allen danach folgenden. -## Diskussion: Den richtigen Guard-Typ wählen +## Diskussion: den richtigen Guard-Typ wählen Die Wahl des Guard-Typs hängt davon ab, *wann* die Prüfung stattfinden soll: @@ -426,7 +428,7 @@ Gibt einer der Guards `false` oder einen `UrlTree` zurück, wird die Navigation const routes: Routes = [ { path: 'admin', - component: AdminComponent, + component: AdminPage, canActivate: [authGuard, adminRoleGuard] }, ]; From 84a94430bf6393fec7efb35cdda7af078d2f37fd Mon Sep 17 00:00:00 2001 From: Johannes Hoppe Date: Tue, 9 Jun 2026 13:40:34 +0200 Subject: [PATCH 04/14] Apply suggestion from @JohannesHoppe --- material/guards/README.md | 866 +++++++++++++++++++------------------- 1 file changed, 433 insertions(+), 433 deletions(-) diff --git a/material/guards/README.md b/material/guards/README.md index e0c4daf1..6b6a0af3 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -1,60 +1,60 @@ ---- -title: 'Guards: Routen absichern' -published: 2026-05-23 -lastModified: 2026-05-23 ---- - -Normalerweise kann jede Route einer Angular-Anwendung uneingeschränkt betreten und wieder verlassen werden. -In komplexeren Anwendungen gibt es allerdings Bereiche, die nur unter bestimmten Umständen aufgerufen werden sollen – z. B. abhängig von Authentifizierung, Berechtigungen oder dem Zustand der Anwendung. -Der Angular-Router bietet dafür ein Feature an, mit dem wir Routen absichern können: *Route Guards*. - -## Inhalt - -[[toc]] - -## Grundlagen zu Guards - -Ein Guard ist eine Funktion, die entscheidet, ob ein Navigationsschritt ausgeführt werden darf oder nicht. -Routen können von Guards „beschützt" werden, sodass stets der Guard durchlaufen werden muss, bevor die Navigation tatsächlich ausgeführt wird. -Auf diese Weise können Guards die Nutzerführung in der Anwendung steuern. - -Die Entscheidung, ob die Navigation durchgeführt wird, wird durch den Rückgabewert der Guard-Funktion ausgedrückt. -Dafür sind diese Varianten möglich: - -- `true`: Die Navigation wird **ausgeführt**. -- `false`: Die Navigation wird **abgebrochen**. -- Typ `UrlTree` oder `RedirectCommand`: Die Navigation wird **abgebrochen**, und es wird zu einer anderen Route navigiert. - -Dieser Rückgabewert kann synchron aus der Funktion zurückgegeben werden, oder er kann in ein Observable oder in eine Promise verpackt werden. -Damit ist es möglich, asynchrone Operationen im Guard zu verarbeiten: Zum Beispiel kann ein HTTP-Request durchgeführt werden, dessen Antwort entscheidet, ob navigiert werden darf. - -> ⚠️ **Wichtig:** Guards steuern die Nutzerführung, aber sie sind kein Sicherheitsfeature! -> Der gesamte kompilierte Code der Anwendung kann vom Browser jederzeit heruntergeladen werden. -> Die Sicherheit der Daten muss immer vom Backend ausgehen: Nur wenn die bedienende Person authentifiziert und autorisiert ist, darf der Server die Daten an den Client senden. -> Guards helfen uns, abhängig von diesen Zuständen die Nutzerführung zu steuern. - -## Varianten von Guards - -Wir unterscheiden verschiedene Arten von Guards, mit denen wir unsere Routen absichern können: - -| Variante | entscheidet, ob … | -|----------|-------------------| -| `CanActivate` | eine Route betreten werden darf | -| `CanActivateChild` | Kind-Routen einer Route betreten werden dürfen | -| `CanDeactivate` | eine Route verlassen werden darf (wegnavigieren) | -| `CanMatch` | eine Route bei der Auswertung berücksichtigt wird | - -Ein Guard wird immer als Funktion entwickelt, die einer bestimmten Signatur folgt. - -## Guards verwenden - -Haben wir eine Guard-Funktion entwickelt, können wir sie auf unsere Routen anwenden: -Guards werden als Eigenschaft einer Routendefinition angegeben und wirken als eine Art Middleware. -Die verwendete Eigenschaft deutet immer auf die Art des Guards hin, z. B. `canActivate`. -Die Guards werden als Array aufgelistet, denn es können auch mehrere Guards für eine Route festgelegt werden. -In diesem Fall werden sie der Reihe nach durchlaufen. - -```typescript +--- +title: 'Guards: Routen absichern' +published: 2026-05-23 +lastModified: 2026-05-23 +--- + +Normalerweise kann jede Route einer Angular-Anwendung uneingeschränkt betreten und wieder verlassen werden. +In komplexeren Anwendungen gibt es allerdings Bereiche, die nur unter bestimmten Umständen aufgerufen werden sollen – z. B. abhängig von Authentifizierung, Berechtigungen oder dem Zustand der Anwendung. +Der Angular-Router bietet dafür ein Feature an, mit dem wir Routen absichern können: *Route Guards*. + +## Inhalt + +[[toc]] + +## Grundlagen zu Guards + +Ein Guard ist eine Funktion, die entscheidet, ob ein Navigationsschritt ausgeführt werden darf oder nicht. +Routen können von Guards „beschützt" werden, sodass stets der Guard durchlaufen werden muss, bevor die Navigation tatsächlich ausgeführt wird. +Auf diese Weise können Guards die Nutzerführung in der Anwendung steuern. + +Die Entscheidung, ob die Navigation durchgeführt wird, wird durch den Rückgabewert der Guard-Funktion ausgedrückt. +Dafür sind diese Varianten möglich: + +- `true`: Die Navigation wird **ausgeführt**. +- `false`: Die Navigation wird **abgebrochen**. +- Typ `UrlTree` oder `RedirectCommand`: Die Navigation wird **abgebrochen**, und es wird zu einer anderen Route navigiert. + +Dieser Rückgabewert kann synchron aus der Funktion zurückgegeben werden, oder er kann in ein Observable oder in eine Promise verpackt werden. +Damit ist es möglich, asynchrone Operationen im Guard zu verarbeiten: Zum Beispiel kann ein HTTP-Request durchgeführt werden, dessen Antwort entscheidet, ob navigiert werden darf. + +> ⚠️ **Wichtig:** Guards steuern die Nutzerführung, aber sie sind kein Sicherheitsfeature! +> Der gesamte kompilierte Code der Anwendung kann vom Browser jederzeit heruntergeladen werden. +> Die Sicherheit der Daten muss immer vom Backend ausgehen: Nur wenn die bedienende Person authentifiziert und autorisiert ist, darf der Server die Daten an den Client senden. +> Guards helfen uns, abhängig von diesen Zuständen die Nutzerführung zu steuern. + +## Varianten von Guards + +Wir unterscheiden verschiedene Arten von Guards, mit denen wir unsere Routen absichern können: + +| Variante | entscheidet, ob … | +|----------|-------------------| +| `CanActivate` | eine Route betreten werden darf | +| `CanActivateChild` | Kind-Routen einer Route betreten werden dürfen | +| `CanDeactivate` | eine Route verlassen werden darf (wegnavigieren) | +| `CanMatch` | eine Route bei der Auswertung berücksichtigt wird | + +Ein Guard wird immer als Funktion entwickelt, die einer bestimmten Signatur folgt. + +## Guards verwenden + +Haben wir eine Guard-Funktion entwickelt, können wir sie auf unsere Routen anwenden: +Guards werden als Eigenschaft einer Routendefinition angegeben und wirken als eine Art Middleware. +Die verwendete Eigenschaft deutet immer auf die Art des Guards hin, z. B. `canActivate`. +Die Guards werden als Array aufgelistet, denn es können auch mehrere Guards für eine Route festgelegt werden. +In diesem Fall werden sie der Reihe nach durchlaufen. + +```typescript export const routes: Routes = [ { path: 'foo', @@ -63,390 +63,390 @@ export const routes: Routes = [ }, { path: 'bar', - component: BarPage + component: BarPage, canDeactivate: [leaveGuard] } ]; -``` - -Rufen wir z. B. die Route mit dem Pfad `foo` auf, wird zunächst die Guard-Funktion `myActivateGuard` ausgeführt. -Liefert sie den Wert `true` zurück, wird die Route geladen, andernfalls wird die Navigation abgebrochen. -Gibt der Guard einen `UrlTree` oder ein `RedirectCommand` zurück, so wird eine neue Navigation zu der neuen Route gestartet. - -## Guards implementieren - -Ein Guard wird als Funktion implementiert, die einer bestimmten Typisierung folgt. -Durch den Typ wird die Signatur der Funktion festgelegt, sodass der Router sicher mit dem Guard arbeiten kann. -Je nach Guard-Variante nimmt die Funktion verschiedene Argumente entgegen. - -Zum Anlegen eines Guards können wir die Angular CLI verwenden: - -```bash -ng generate guard foo --guardType CanActivate -ng generate guard bar --guardType CanMatch -``` - -### CanActivate: Darf die Route aktiviert werden? - -Mit einem `CanActivate`-Guard können wir prüfen, ob eine bestimmte Route betreten werden darf. -Wir verwenden den Typ `CanActivateFn`, um die Guard-Funktion zu typisieren. -Sie erhält zwei optionale Argumente: - -- `ActivatedRouteSnapshot`: Informationen zur angefragten Route, z. B. Routenparameter. -- `RouterStateSnapshot`: Der gesamte Zustand des Routers. - -Wollen wir im Guard auf Services zugreifen, z. B. um die Entscheidung abhängig von einem zentralen Zustand zu machen, verwenden wir die Funktion `inject()`. - -```typescript -import { inject } from '@angular/core'; -import { CanActivateFn } from '@angular/router'; - -export const myActivateGuard: CanActivateFn = - (route, state) => { - // Routenparameter lesen - const foo = route.paramMap.get('foo'); - - // Service injizieren - const authService = inject(AuthService); - - // Entscheidung treffen (true / false) - return authService.isAuthenticated(); - }; -``` - +``` + +Rufen wir z. B. die Route mit dem Pfad `foo` auf, wird zunächst die Guard-Funktion `myActivateGuard` ausgeführt. +Liefert sie den Wert `true` zurück, wird die Route geladen, andernfalls wird die Navigation abgebrochen. +Gibt der Guard einen `UrlTree` oder ein `RedirectCommand` zurück, so wird eine neue Navigation zu der neuen Route gestartet. + +## Guards implementieren + +Ein Guard wird als Funktion implementiert, die einer bestimmten Typisierung folgt. +Durch den Typ wird die Signatur der Funktion festgelegt, sodass der Router sicher mit dem Guard arbeiten kann. +Je nach Guard-Variante nimmt die Funktion verschiedene Argumente entgegen. + +Zum Anlegen eines Guards können wir die Angular CLI verwenden: + +```bash +ng generate guard foo --guardType CanActivate +ng generate guard bar --guardType CanMatch +``` + +### CanActivate: Darf die Route aktiviert werden? + +Mit einem `CanActivate`-Guard können wir prüfen, ob eine bestimmte Route betreten werden darf. +Wir verwenden den Typ `CanActivateFn`, um die Guard-Funktion zu typisieren. +Sie erhält zwei optionale Argumente: + +- `ActivatedRouteSnapshot`: Informationen zur angefragten Route, z. B. Routenparameter. +- `RouterStateSnapshot`: Der gesamte Zustand des Routers. + +Wollen wir im Guard auf Services zugreifen, z. B. um die Entscheidung abhängig von einem zentralen Zustand zu machen, verwenden wir die Funktion `inject()`. + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn } from '@angular/router'; + +export const myActivateGuard: CanActivateFn = + (route, state) => { + // Routenparameter lesen + const foo = route.paramMap.get('foo'); + + // Service injizieren + const authService = inject(AuthService); + + // Entscheidung treffen (true / false) + return authService.isAuthenticated(); + }; +``` + ### Umleitung mit `UrlTree` - -Wenn eine Navigation nicht erlaubt ist, kann es sinnvoll sein, stattdessen zu einer anderen Route umzuleiten. -Dafür können wir aus dem Guard ein Objekt vom Typ `UrlTree` zurückgeben. -Dieser `UrlTree` ist eine von Angular geparste Route und gibt dem Router die Anweisung, zu dieser Route zu navigieren. -Er lässt sich mithilfe des Routers erzeugen: Die Methode `Router.parseUrl()` wandelt eine URL in einen `UrlTree` um: - -```typescript -import { inject } from '@angular/core'; -import { CanActivateFn, Router } from '@angular/router'; - -export const myActivateGuard: CanActivateFn = () => { - const authService = inject(AuthService); - - if (authService.isAuthenticated()) { - return true; - } - - const router = inject(Router); - return router.parseUrl('/login'); -}; -``` - -Alternativ können wir einen `UrlTree` auch mit der Methode `Router.createUrlTree()` erzeugen. -Hier übergeben wir ein Array von Routensegmenten. + +Wenn eine Navigation nicht erlaubt ist, kann es sinnvoll sein, stattdessen zu einer anderen Route umzuleiten. +Dafür können wir aus dem Guard ein Objekt vom Typ `UrlTree` zurückgeben. +Dieser `UrlTree` ist eine von Angular geparste Route und gibt dem Router die Anweisung, zu dieser Route zu navigieren. +Er lässt sich mithilfe des Routers erzeugen: Die Methode `Router.parseUrl()` wandelt eine URL in einen `UrlTree` um: + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; + +export const myActivateGuard: CanActivateFn = () => { + const authService = inject(AuthService); + + if (authService.isAuthenticated()) { + return true; + } + + const router = inject(Router); + return router.parseUrl('/login'); +}; +``` + +Alternativ können wir einen `UrlTree` auch mit der Methode `Router.createUrlTree()` erzeugen. +Hier übergeben wir ein Array von Routensegmenten. Außerdem können wir im zweiten Argument ein Objekt mit weiteren Optionen notieren, z. B. den Bezugspunkt für eine relative URL. - -```typescript -import { inject } from '@angular/core'; -import { CanActivateFn, Router } from '@angular/router'; - -export const myActivateGuard: CanActivateFn = () => { - // ... - const router = inject(Router); - return router.createUrlTree(['login']); -}; -``` - -### Umleitung mit RedirectCommand - -Seit Angular 18.1 gibt es eine weitere Möglichkeit, eine Umleitung aus einem Guard heraus auszulösen: das `RedirectCommand`. -Im Gegensatz zum `UrlTree` können wir damit zusätzliche Navigationsoptionen angeben, z. B. `replaceUrl` oder `skipLocationChange`: - -```typescript -import { inject } from '@angular/core'; -import { CanActivateFn, Router, RedirectCommand } from '@angular/router'; - -export const myActivateGuard: CanActivateFn = () => { - const authService = inject(AuthService); - - if (authService.isAuthenticated()) { - return true; - } - - const router = inject(Router); - const urlTree = router.parseUrl('/login'); - return new RedirectCommand(urlTree, { replaceUrl: true }); -}; -``` - + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; + +export const myActivateGuard: CanActivateFn = () => { + // ... + const router = inject(Router); + return router.createUrlTree(['login']); +}; +``` + +### Umleitung mit RedirectCommand + +Seit Angular 18.1 gibt es eine weitere Möglichkeit, eine Umleitung aus einem Guard heraus auszulösen: das `RedirectCommand`. +Im Gegensatz zum `UrlTree` können wir damit zusätzliche Navigationsoptionen angeben, z. B. `replaceUrl` oder `skipLocationChange`: + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn, Router, RedirectCommand } from '@angular/router'; + +export const myActivateGuard: CanActivateFn = () => { + const authService = inject(AuthService); + + if (authService.isAuthenticated()) { + return true; + } + + const router = inject(Router); + const urlTree = router.parseUrl('/login'); + return new RedirectCommand(urlTree, { replaceUrl: true }); +}; +``` + ### Warum `UrlTree` statt `Router.navigate()`? - -Theoretisch könnten wir aus dem Guard heraus auch direkt die Methode `Router.navigate()` aufrufen, um zu einer anderen Route zu wechseln. -Praktisch hat der `UrlTree` (bzw. das `RedirectCommand`) allerdings einen entscheidenden Vorteil, wenn mehrere Guards aktiv sind, die asynchron arbeiten. -In einer solchen Konstellation ist nie klar, welcher der Guards wann eine Antwort liefert und damit über das Ziel der Navigation entscheidet. - -Verwenden wir einen `UrlTree` als Rückgabewert, verhält sich der Router deterministisch: -Die Guards, die näher an der Wurzel der Routenhierarchie aktiv sind, haben eine höhere Priorität als die Guards, die tiefer im Baum platziert sind. -Der Router kümmert sich um die Priorisierung und Weiterleitung. - -Verwende deshalb bitte immer einen `UrlTree` oder ein `RedirectCommand`, um aus einem Guard heraus zu einer anderen Route zu navigieren. - -### CanDeactivate: Darf die aktive Route verlassen werden? - -Mit einem `CanDeactivate`-Guard können wir prüfen, ob die gerade aktive Route verlassen werden darf. -Die Guard-Funktion erhält als erstes Argument eine Referenz auf die Komponente, die durch die Navigation verlassen wird. -Der Typ dieser Komponente wird mit dem generischen Typparameter `T` im Typ `CanDeactivateFn` angegeben. - -Die Funktion erhält die gesamte Instanz der Komponente als Argument: -Wir können also Daten aus der Komponente abfragen und die Entscheidung abhängig vom Zustand machen. -Das ist z. B. sinnvoll, um zu prüfen, ob Änderungen in einem Formular vorgenommen wurden, die nicht verworfen werden sollen. - -```typescript -import { CanDeactivateFn } from '@angular/router'; -import { MyComponent } from './my.component'; - -export const leaveGuard: CanDeactivateFn = - (component) => { - return !component.hasUnsavedChanges; - }; -``` - -Wenn wir den Guard in der Route verwenden, wird die Navigation weg von der Komponente `MyComponent` nur ausgeführt, wenn das Property `hasUnsavedChanges` in der Komponente den Wert `false` hat. -Diese Eigenschaft ist selbst definiert und muss natürlich innerhalb der Komponente gesteuert werden. - -> **Tipp: Guard wiederverwenden** -> -> Durch den generischen Typparameter ist der gezeigte `CanDeactivate`-Guard spezifisch für eine bestimmte Komponente. -> Um das zu vermeiden, empfehlen wir, ein Interface anzulegen, das die benötigte Schnittstelle der Komponente vorgibt, z. B. das Property `hasUnsavedChanges`. -> Alle Komponenten, die diese Schnittstelle für den Guard anbieten, müssen das Interface implementieren. -> Der Guard verwendet in seinem Typparameter dann ebenfalls das Interface. So kann der Guard mit verschiedenen Komponenten arbeiten und ist nicht nur für eine einzelne Komponente verwendbar. - -```typescript -export interface HasUnsavedChanges { - hasUnsavedChanges: boolean; -} - -export const leaveGuard: CanDeactivateFn = - (component) => { - if (component.hasUnsavedChanges) { - return confirm('Du hast ungespeicherte Änderungen. Möchtest du die Seite wirklich verlassen?'); - } - return true; - }; -``` - -### CanActivateChild: Dürfen Kind-Routen betreten werden? - -Ein Guard mit dem Typ `CanActivateChildFn` entscheidet, ob zu den Kindern der betreffenden Route navigiert werden darf. -Dieser Guard wird für *alle* Kind-Routen ausgeführt, die mit `children` definiert sind. -Das ist besonders nützlich, um eine ganze Gruppe von Routen mit einer einzigen Prüfung abzusichern. - -```typescript -import { inject } from '@angular/core'; -import { CanActivateChildFn } from '@angular/router'; - -export const adminChildGuard: CanActivateChildFn = - (childRoute, state) => { - const authService = inject(AuthService); - return authService.hasRole('admin'); - }; -``` - -In der Routenkonfiguration wird der Guard auf der Elternroute notiert: - -```typescript -const routes: Routes = [ - { - path: 'users', - canActivateChild: [adminChildGuard], - children: [ - { path: 'list', component: UserListComponent }, - { path: 'detail/:id', component: UserDetailComponent }, - ], - }, -]; -``` - -### CanMatch: Wird die Route berücksichtigt? - -Ein Guard mit dem Typ `CanMatchFn` entscheidet, ob eine Route bei der Auswertung berücksichtigt wird. -Navigieren wir zu einer URL, werden alle Routen von oben nach unten abgearbeitet. -Für jede Route wird geprüft, ob sie zur angeforderten URL passt – und die erste passende Route wird geladen. -Mit `CanMatch`-Guards können wir entscheiden, ob eine Route dabei überhaupt ausgewertet oder übersprungen wird. - -Die Guard-Funktion erhält zwei Argumente: das gesamte Routen-Objekt und die angeforderte URL in Form eines Arrays von URL-Segmenten. -Gibt die Funktion `false` zurück, wird die betreffende Route bei der Auswertung übersprungen – der Router versucht dann, eine andere passende Route zu finden. -Auch dieser Guard kann einen `UrlTree` zurückgeben, um eine neue Navigation anzustoßen. - -```typescript -import { inject } from '@angular/core'; -import { CanMatchFn } from '@angular/router'; - -export const myMatchGuard: CanMatchFn = - (route, segments) => { - return inject(AuthService).isAuthenticated(); - }; -``` - -Da wir auf diese Weise bestimmte Routen von der Auswertung ausschließen können, eignet sich `CanMatch` für ein besonderes Szenario: -Wir können mehrere Varianten einer Route anbieten, die auf verschiedene Komponenten zeigen. -Entscheidet der Guard, dass die erste Route nicht berücksichtigt wird, wird die zweite verwendet. - -```typescript -const routes: Routes = [ - { - path: 'myfeature', - component: FeatureComponent, - canMatch: [myMatchGuard] - }, - { - path: 'myfeature', - component: AnonymousFeatureComponent - } -]; -``` - -Ist also im `AuthService` das Property `isAuthenticated` auf `true` gesetzt, wird die erste Route mit der `FeatureComponent` geladen. -Steht der Zustand auf `false`, wird die `AnonymousFeatureComponent` angezeigt. - -Dieses Muster eignet sich hervorragend für Feature Flags, A/B-Testing oder bedingte Routenauswahl. - -## Guards inline definieren - -In allen bisherigen Beispielen haben wir die Funktionen separat abgelegt, sodass wir sie in den Routen referenzieren können. -Wollen wir einen Guard nur einmalig nutzen, können wir die Funktion auch direkt in der Route definieren: - -```typescript -const routes: Routes = [ - { - path: 'bar', - component: MyComponent, - canDeactivate: [ - (comp: MyComponent) => !comp.hasUnsavedChanges, - ] - }, - { - path: 'secret', - component: SecretComponent, - canActivate: [() => inject(AuthService).isAuthenticated()] - } -]; -``` - -## Praxisbeispiel: Admin-Bereich absichern - -Schauen wir uns ein vollständiges Beispiel an. -Wir wollen den Administrationsbereich einer Anwendung absichern, sodass er nur für angemeldete Personen zugänglich ist. -Die Information zum Authentifizierungsstatus erhalten wir aus einem `AuthService`. - -### Guard implementieren - -Wir erstellen einen `CanActivate`-Guard, der den Authentifizierungsstatus prüft. -Ist die Person angemeldet, erlauben wir die Navigation. -Falls nicht, leiten wir zur Startseite um und zeigen eine Meldung an: - -```typescript -import { inject } from '@angular/core'; -import { CanActivateFn, Router } from '@angular/router'; - -export const authGuard: CanActivateFn = () => { - const authService = inject(AuthService); - const router = inject(Router); - - if (authService.isAuthenticated()) { - return true; - } - - alert('Bitte melde dich an, um den Admin-Bereich zu betreten.'); - return router.parseUrl('/home'); -}; -``` - -### Guard in der Route verwenden - -In der Routenkonfiguration fügen wir die Eigenschaft `canActivate` hinzu und geben die Guard-Funktion an. -Damit wird der Guard ausgeführt, bevor die Route geladen wird: - -```typescript -const routes: Routes = [ - { - path: 'admin', - loadChildren: () => import('./admin/admin.routes'), - canActivate: [authGuard] - }, - // ... -]; -``` - -Wenn wir die Basisroute für das Lazy Loading mit dem Guard sichern, sind auch alle darunter folgenden Routen abgedeckt. - -### Alternative: Asynchroner Guard mit Observable - -In einer produktiven Anwendung erhalten wir den Status der Authentifizierung möglicherweise nicht synchron. -Unser `AuthService` bietet die Information zusätzlich über ein Observable an. + +Theoretisch könnten wir aus dem Guard heraus auch direkt die Methode `Router.navigate()` aufrufen, um zu einer anderen Route zu wechseln. +Praktisch hat der `UrlTree` (bzw. das `RedirectCommand`) allerdings einen entscheidenden Vorteil, wenn mehrere Guards aktiv sind, die asynchron arbeiten. +In einer solchen Konstellation ist nie klar, welcher der Guards wann eine Antwort liefert und damit über das Ziel der Navigation entscheidet. + +Verwenden wir einen `UrlTree` als Rückgabewert, verhält sich der Router deterministisch: +Die Guards, die näher an der Wurzel der Routenhierarchie aktiv sind, haben eine höhere Priorität als die Guards, die tiefer im Baum platziert sind. +Der Router kümmert sich um die Priorisierung und Weiterleitung. + +Verwende deshalb bitte immer einen `UrlTree` oder ein `RedirectCommand`, um aus einem Guard heraus zu einer anderen Route zu navigieren. + +### CanDeactivate: Darf die aktive Route verlassen werden? + +Mit einem `CanDeactivate`-Guard können wir prüfen, ob die gerade aktive Route verlassen werden darf. +Die Guard-Funktion erhält als erstes Argument eine Referenz auf die Komponente, die durch die Navigation verlassen wird. +Der Typ dieser Komponente wird mit dem generischen Typparameter `T` im Typ `CanDeactivateFn` angegeben. + +Die Funktion erhält die gesamte Instanz der Komponente als Argument: +Wir können also Daten aus der Komponente abfragen und die Entscheidung abhängig vom Zustand machen. +Das ist z. B. sinnvoll, um zu prüfen, ob Änderungen in einem Formular vorgenommen wurden, die nicht verworfen werden sollen. + +```typescript +import { CanDeactivateFn } from '@angular/router'; +import { MyComponent } from './my.component'; + +export const leaveGuard: CanDeactivateFn = + (component) => { + return !component.hasUnsavedChanges; + }; +``` + +Wenn wir den Guard in der Route verwenden, wird die Navigation weg von der Komponente `MyComponent` nur ausgeführt, wenn das Property `hasUnsavedChanges` in der Komponente den Wert `false` hat. +Diese Eigenschaft ist selbst definiert und muss natürlich innerhalb der Komponente gesteuert werden. + +> **Tipp: Guard wiederverwenden** +> +> Durch den generischen Typparameter ist der gezeigte `CanDeactivate`-Guard spezifisch für eine bestimmte Komponente. +> Um das zu vermeiden, empfehlen wir, ein Interface anzulegen, das die benötigte Schnittstelle der Komponente vorgibt, z. B. das Property `hasUnsavedChanges`. +> Alle Komponenten, die diese Schnittstelle für den Guard anbieten, müssen das Interface implementieren. +> Der Guard verwendet in seinem Typparameter dann ebenfalls das Interface. So kann der Guard mit verschiedenen Komponenten arbeiten und ist nicht nur für eine einzelne Komponente verwendbar. + +```typescript +export interface HasUnsavedChanges { + hasUnsavedChanges: boolean; +} + +export const leaveGuard: CanDeactivateFn = + (component) => { + if (component.hasUnsavedChanges) { + return confirm('Du hast ungespeicherte Änderungen. Möchtest du die Seite wirklich verlassen?'); + } + return true; + }; +``` + +### CanActivateChild: Dürfen Kind-Routen betreten werden? + +Ein Guard mit dem Typ `CanActivateChildFn` entscheidet, ob zu den Kindern der betreffenden Route navigiert werden darf. +Dieser Guard wird für *alle* Kind-Routen ausgeführt, die mit `children` definiert sind. +Das ist besonders nützlich, um eine ganze Gruppe von Routen mit einer einzigen Prüfung abzusichern. + +```typescript +import { inject } from '@angular/core'; +import { CanActivateChildFn } from '@angular/router'; + +export const adminChildGuard: CanActivateChildFn = + (childRoute, state) => { + const authService = inject(AuthService); + return authService.hasRole('admin'); + }; +``` + +In der Routenkonfiguration wird der Guard auf der Elternroute notiert: + +```typescript +const routes: Routes = [ + { + path: 'users', + canActivateChild: [adminChildGuard], + children: [ + { path: 'list', component: UserListComponent }, + { path: 'detail/:id', component: UserDetailComponent }, + ], + }, +]; +``` + +### CanMatch: Wird die Route berücksichtigt? + +Ein Guard mit dem Typ `CanMatchFn` entscheidet, ob eine Route bei der Auswertung berücksichtigt wird. +Navigieren wir zu einer URL, werden alle Routen von oben nach unten abgearbeitet. +Für jede Route wird geprüft, ob sie zur angeforderten URL passt – und die erste passende Route wird geladen. +Mit `CanMatch`-Guards können wir entscheiden, ob eine Route dabei überhaupt ausgewertet oder übersprungen wird. + +Die Guard-Funktion erhält zwei Argumente: das gesamte Routen-Objekt und die angeforderte URL in Form eines Arrays von URL-Segmenten. +Gibt die Funktion `false` zurück, wird die betreffende Route bei der Auswertung übersprungen – der Router versucht dann, eine andere passende Route zu finden. +Auch dieser Guard kann einen `UrlTree` zurückgeben, um eine neue Navigation anzustoßen. + +```typescript +import { inject } from '@angular/core'; +import { CanMatchFn } from '@angular/router'; + +export const myMatchGuard: CanMatchFn = + (route, segments) => { + return inject(AuthService).isAuthenticated(); + }; +``` + +Da wir auf diese Weise bestimmte Routen von der Auswertung ausschließen können, eignet sich `CanMatch` für ein besonderes Szenario: +Wir können mehrere Varianten einer Route anbieten, die auf verschiedene Komponenten zeigen. +Entscheidet der Guard, dass die erste Route nicht berücksichtigt wird, wird die zweite verwendet. + +```typescript +const routes: Routes = [ + { + path: 'myfeature', + component: FeatureComponent, + canMatch: [myMatchGuard] + }, + { + path: 'myfeature', + component: AnonymousFeatureComponent + } +]; +``` + +Ist also im `AuthService` das Property `isAuthenticated` auf `true` gesetzt, wird die erste Route mit der `FeatureComponent` geladen. +Steht der Zustand auf `false`, wird die `AnonymousFeatureComponent` angezeigt. + +Dieses Muster eignet sich hervorragend für Feature Flags, A/B-Testing oder bedingte Routenauswahl. + +## Guards inline definieren + +In allen bisherigen Beispielen haben wir die Funktionen separat abgelegt, sodass wir sie in den Routen referenzieren können. +Wollen wir einen Guard nur einmalig nutzen, können wir die Funktion auch direkt in der Route definieren: + +```typescript +const routes: Routes = [ + { + path: 'bar', + component: MyComponent, + canDeactivate: [ + (comp: MyComponent) => !comp.hasUnsavedChanges, + ] + }, + { + path: 'secret', + component: SecretComponent, + canActivate: [() => inject(AuthService).isAuthenticated()] + } +]; +``` + +## Praxisbeispiel: Admin-Bereich absichern + +Schauen wir uns ein vollständiges Beispiel an. +Wir wollen den Administrationsbereich einer Anwendung absichern, sodass er nur für angemeldete Personen zugänglich ist. +Die Information zum Authentifizierungsstatus erhalten wir aus einem `AuthService`. + +### Guard implementieren + +Wir erstellen einen `CanActivate`-Guard, der den Authentifizierungsstatus prüft. +Ist die Person angemeldet, erlauben wir die Navigation. +Falls nicht, leiten wir zur Startseite um und zeigen eine Meldung an: + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; + +export const authGuard: CanActivateFn = () => { + const authService = inject(AuthService); + const router = inject(Router); + + if (authService.isAuthenticated()) { + return true; + } + + alert('Bitte melde dich an, um den Admin-Bereich zu betreten.'); + return router.parseUrl('/home'); +}; +``` + +### Guard in der Route verwenden + +In der Routenkonfiguration fügen wir die Eigenschaft `canActivate` hinzu und geben die Guard-Funktion an. +Damit wird der Guard ausgeführt, bevor die Route geladen wird: + +```typescript +const routes: Routes = [ + { + path: 'admin', + loadChildren: () => import('./admin/admin.routes'), + canActivate: [authGuard] + }, + // ... +]; +``` + +Wenn wir die Basisroute für das Lazy Loading mit dem Guard sichern, sind auch alle darunter folgenden Routen abgedeckt. + +### Alternative: Asynchroner Guard mit Observable + +In einer produktiven Anwendung erhalten wir den Status der Authentifizierung möglicherweise nicht synchron. +Unser `AuthService` bietet die Information zusätzlich über ein Observable an. In diesem Fall können wir den Guard asynchron implementieren. Er gibt dann ein Observable oder eine Promise zurück. Der Router wartet auf die asynchrone Operation und entscheidet dann mit dem Ergebnis, ob und wie die Navigation ausgeführt wird. - -```typescript -import { inject } from '@angular/core'; -import { CanActivateFn, Router } from '@angular/router'; -import { map, take } from 'rxjs'; - -export const authGuard: CanActivateFn = () => { - const authService = inject(AuthService); - const router = inject(Router); - - return authService.isAuthenticated$.pipe( - take(1), - map(isAuthenticated => { - if (isAuthenticated) { - return true; - } - return router.parseUrl('/login'); - }) - ); -}; -``` - -Es ist wichtig, dass wir die Länge des Datenstroms mithilfe von `take(1)` begrenzen: Wir sind nur an einem einzigen Wert interessiert, nicht an allen danach folgenden. - + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; +import { map, take } from 'rxjs'; + +export const authGuard: CanActivateFn = () => { + const authService = inject(AuthService); + const router = inject(Router); + + return authService.isAuthenticated$.pipe( + take(1), + map(isAuthenticated => { + if (isAuthenticated) { + return true; + } + return router.parseUrl('/login'); + }) + ); +}; +``` + +Es ist wichtig, dass wir die Länge des Datenstroms mithilfe von `take(1)` begrenzen: Wir sind nur an einem einzigen Wert interessiert, nicht an allen danach folgenden. + ## Diskussion: den richtigen Guard-Typ wählen - -Die Wahl des Guard-Typs hängt davon ab, *wann* die Prüfung stattfinden soll: - -- **`CanActivate`**: Die Route wird zunächst aufgelöst (und bei Lazy Loading heruntergeladen). Bevor die Komponente aktiviert wird, entscheidet der Guard. -- **`CanMatch`**: Die Prüfung findet statt, *bevor* die Route überhaupt aufgelöst wird. Bei Lazy Loading wird das Bundle nur heruntergeladen, wenn der Guard es erlaubt. -- **`CanActivateChild`**: Sichert alle Kind-Routen einer Elternroute mit einer einzigen Prüfung ab. -- **`CanDeactivate`**: Prüft, ob die aktuelle Route verlassen werden darf. - -Für die Absicherung von Lazy-Loading-Routen empfehlen wir `CanMatch`, da so das Bundle nur bei Bedarf geladen wird. -Für einfache Authentifizierungsprüfungen auf einzelnen Routen ist `CanActivate` die gängigste Wahl. - -## Mehrere Guards kombinieren - -Guards werden als Array angegeben und in der definierten Reihenfolge ausgeführt. -Alle Guards müssen `true` zurückgeben, damit die Navigation stattfindet. -Gibt einer der Guards `false` oder einen `UrlTree` zurück, wird die Navigation abgebrochen bzw. umgeleitet. - -```typescript -const routes: Routes = [ - { - path: 'admin', + +Die Wahl des Guard-Typs hängt davon ab, *wann* die Prüfung stattfinden soll: + +- **`CanActivate`**: Die Route wird zunächst aufgelöst (und bei Lazy Loading heruntergeladen). Bevor die Komponente aktiviert wird, entscheidet der Guard. +- **`CanMatch`**: Die Prüfung findet statt, *bevor* die Route überhaupt aufgelöst wird. Bei Lazy Loading wird das Bundle nur heruntergeladen, wenn der Guard es erlaubt. +- **`CanActivateChild`**: Sichert alle Kind-Routen einer Elternroute mit einer einzigen Prüfung ab. +- **`CanDeactivate`**: Prüft, ob die aktuelle Route verlassen werden darf. + +Für die Absicherung von Lazy-Loading-Routen empfehlen wir `CanMatch`, da so das Bundle nur bei Bedarf geladen wird. +Für einfache Authentifizierungsprüfungen auf einzelnen Routen ist `CanActivate` die gängigste Wahl. + +## Mehrere Guards kombinieren + +Guards werden als Array angegeben und in der definierten Reihenfolge ausgeführt. +Alle Guards müssen `true` zurückgeben, damit die Navigation stattfindet. +Gibt einer der Guards `false` oder einen `UrlTree` zurück, wird die Navigation abgebrochen bzw. umgeleitet. + +```typescript +const routes: Routes = [ + { + path: 'admin', component: AdminPage, - canActivate: [authGuard, adminRoleGuard] - }, -]; -``` - -In diesem Beispiel muss die Person sowohl authentifiziert sein als auch die Admin-Rolle besitzen, um die Route zu betreten. - -## Zusammenfassung - -- Mit Guards können Routen abgesichert werden, sodass die Navigation unter bestimmten Umständen abgebrochen wird. -- Ein Guard ist eine Funktion, die entscheidet, ob die Navigation ausgeführt werden darf. -- Es gibt vier Arten von Guards: - - `CanActivate` – beim Aufruf einer Route - - `CanDeactivate` – beim Verlassen einer Route - - `CanActivateChild` – beim Aufruf einer Kind-Route - - `CanMatch` – beim Auswerten der Route -- Der Rückgabewert eines Guards ist ein `boolean`, ein `UrlTree` oder ein `RedirectCommand`. Der Rückgabewert kann auch asynchron von einem Observable oder einer Promise geliefert werden. -- Gibt der Guard `true` zurück, wird die Navigation ausgeführt, bei `false` wird sie abgebrochen. -- Gibt der Guard einen `UrlTree` oder ein `RedirectCommand` zurück, wird eine neue Navigation zur enthaltenen Route gestartet. -- Guards werden als Eigenschaft einer Routendefinition notiert und wirken dann auf diese Route. -- Guards steuern die Nutzerführung, aber sie sind kein Sicherheitsfeature. Die Absicherung muss immer serverseitig erfolgen. + canActivate: [authGuard, adminRoleGuard] + }, +]; +``` + +In diesem Beispiel muss die Person sowohl authentifiziert sein als auch die Admin-Rolle besitzen, um die Route zu betreten. + +## Zusammenfassung + +- Mit Guards können Routen abgesichert werden, sodass die Navigation unter bestimmten Umständen abgebrochen wird. +- Ein Guard ist eine Funktion, die entscheidet, ob die Navigation ausgeführt werden darf. +- Es gibt vier Arten von Guards: + - `CanActivate` – beim Aufruf einer Route + - `CanDeactivate` – beim Verlassen einer Route + - `CanActivateChild` – beim Aufruf einer Kind-Route + - `CanMatch` – beim Auswerten der Route +- Der Rückgabewert eines Guards ist ein `boolean`, ein `UrlTree` oder ein `RedirectCommand`. Der Rückgabewert kann auch asynchron von einem Observable oder einer Promise geliefert werden. +- Gibt der Guard `true` zurück, wird die Navigation ausgeführt, bei `false` wird sie abgebrochen. +- Gibt der Guard einen `UrlTree` oder ein `RedirectCommand` zurück, wird eine neue Navigation zur enthaltenen Route gestartet. +- Guards werden als Eigenschaft einer Routendefinition notiert und wirken dann auf diese Route. +- Guards steuern die Nutzerführung, aber sie sind kein Sicherheitsfeature. Die Absicherung muss immer serverseitig erfolgen. From acd64e62072dfd0443b60d72f3257c2201a6a82a Mon Sep 17 00:00:00 2001 From: Johannes Hoppe Date: Tue, 9 Jun 2026 13:42:31 +0200 Subject: [PATCH 05/14] Apply suggestion from @JohannesHoppe --- material/guards/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/material/guards/README.md b/material/guards/README.md index 6b6a0af3..b05ea54d 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -154,7 +154,7 @@ export const myActivateGuard: CanActivateFn = () => { ### Umleitung mit RedirectCommand -Seit Angular 18.1 gibt es eine weitere Möglichkeit, eine Umleitung aus einem Guard heraus auszulösen: das `RedirectCommand`. +Seit Angular 18 gibt es eine weitere Möglichkeit, eine Umleitung aus einem Guard heraus auszulösen: das `RedirectCommand`. Im Gegensatz zum `UrlTree` können wir damit zusätzliche Navigationsoptionen angeben, z. B. `replaceUrl` oder `skipLocationChange`: ```typescript From 4a156b81423b52c9924ca005be5d356724a0a41d Mon Sep 17 00:00:00 2001 From: Ferdinand Malcher Date: Tue, 9 Jun 2026 14:52:25 +0200 Subject: [PATCH 06/14] Update material/guards/README.md Co-authored-by: Johannes Hoppe --- material/guards/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/material/guards/README.md b/material/guards/README.md index b05ea54d..1f4f50f4 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -90,7 +90,7 @@ ng generate guard bar --guardType CanMatch Mit einem `CanActivate`-Guard können wir prüfen, ob eine bestimmte Route betreten werden darf. Wir verwenden den Typ `CanActivateFn`, um die Guard-Funktion zu typisieren. -Sie erhält zwei optionale Argumente: +Sie kann zwei Argumente entgegennehmen: - `ActivatedRouteSnapshot`: Informationen zur angefragten Route, z. B. Routenparameter. - `RouterStateSnapshot`: Der gesamte Zustand des Routers. From c2f1fc7de55a16d6446770352e6b94b0495285e6 Mon Sep 17 00:00:00 2001 From: Ferdinand Malcher Date: Tue, 9 Jun 2026 14:53:54 +0200 Subject: [PATCH 07/14] Apply suggestions from code review Co-authored-by: Johannes Hoppe --- material/guards/README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/material/guards/README.md b/material/guards/README.md index 1f4f50f4..c2b32a98 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -30,7 +30,7 @@ Damit ist es möglich, asynchrone Operationen im Guard zu verarbeiten: Zum Beisp > ⚠️ **Wichtig:** Guards steuern die Nutzerführung, aber sie sind kein Sicherheitsfeature! > Der gesamte kompilierte Code der Anwendung kann vom Browser jederzeit heruntergeladen werden. -> Die Sicherheit der Daten muss immer vom Backend ausgehen: Nur wenn die bedienende Person authentifiziert und autorisiert ist, darf der Server die Daten an den Client senden. +> Die Sicherheit der Daten muss immer vom Backend ausgehen: Nur wenn der Client authentifiziert ist, darf der Server die Daten herausgeben oder geschützte Aktionen durchführen. > Guards helfen uns, abhängig von diesen Zuständen die Nutzerführung zu steuern. ## Varianten von Guards @@ -118,13 +118,14 @@ export const myActivateGuard: CanActivateFn = Wenn eine Navigation nicht erlaubt ist, kann es sinnvoll sein, stattdessen zu einer anderen Route umzuleiten. Dafür können wir aus dem Guard ein Objekt vom Typ `UrlTree` zurückgeben. -Dieser `UrlTree` ist eine von Angular geparste Route und gibt dem Router die Anweisung, zu dieser Route zu navigieren. +Ein `UrlTree` ist die interne Repräsentation einer Route und gibt dem Router die Anweisung, zu dieser Route zu navigieren. Er lässt sich mithilfe des Routers erzeugen: Die Methode `Router.parseUrl()` wandelt eine URL in einen `UrlTree` um: ```typescript import { inject } from '@angular/core'; import { CanActivateFn, Router } from '@angular/router'; +// Variante 1: nur true/false export const myActivateGuard: CanActivateFn = () => { const authService = inject(AuthService); @@ -145,6 +146,7 @@ Außerdem können wir im zweiten Argument ein Objekt mit weiteren Optionen notie import { inject } from '@angular/core'; import { CanActivateFn, Router } from '@angular/router'; +// Variante 2: Umleitung über UrlTree export const myActivateGuard: CanActivateFn = () => { // ... const router = inject(Router); @@ -161,6 +163,7 @@ Im Gegensatz zum `UrlTree` können wir damit zusätzliche Navigationsoptionen an import { inject } from '@angular/core'; import { CanActivateFn, Router, RedirectCommand } from '@angular/router'; +// Variante 3: Umleitung über RedirectCommand mit Navigationsoptionen export const myActivateGuard: CanActivateFn = () => { const authService = inject(AuthService); @@ -202,23 +205,23 @@ import { MyComponent } from './my.component'; export const leaveGuard: CanDeactivateFn = (component) => { - return !component.hasUnsavedChanges; + return !component.hasUnsavedChanges(); }; ``` -Wenn wir den Guard in der Route verwenden, wird die Navigation weg von der Komponente `MyComponent` nur ausgeführt, wenn das Property `hasUnsavedChanges` in der Komponente den Wert `false` hat. +Wenn wir den Guard in der Route verwenden, wird das Verlassen der Komponente `MyComponent` nur erlaubt, wenn das Signal `hasUnsavedChanges` in der Komponente den Wert `false` hat. Diese Eigenschaft ist selbst definiert und muss natürlich innerhalb der Komponente gesteuert werden. > **Tipp: Guard wiederverwenden** > > Durch den generischen Typparameter ist der gezeigte `CanDeactivate`-Guard spezifisch für eine bestimmte Komponente. -> Um das zu vermeiden, empfehlen wir, ein Interface anzulegen, das die benötigte Schnittstelle der Komponente vorgibt, z. B. das Property `hasUnsavedChanges`. +> Um das zu vermeiden, empfehlen wir, ein Interface anzulegen, das die benötigte Schnittstelle der Komponente vorgibt, z. B. das Property `hasUnsavedChanges` mit einem Signal. > Alle Komponenten, die diese Schnittstelle für den Guard anbieten, müssen das Interface implementieren. > Der Guard verwendet in seinem Typparameter dann ebenfalls das Interface. So kann der Guard mit verschiedenen Komponenten arbeiten und ist nicht nur für eine einzelne Komponente verwendbar. ```typescript export interface HasUnsavedChanges { - hasUnsavedChanges: boolean; + hasUnsavedChanges: Signal; } export const leaveGuard: CanDeactivateFn = From a0ec7d3e4842a18955775bee95016d4b898ccf46 Mon Sep 17 00:00:00 2001 From: Ferdinand Malcher Date: Tue, 9 Jun 2026 14:54:09 +0200 Subject: [PATCH 08/14] Update material/guards/README.md Co-authored-by: Johannes Hoppe --- material/guards/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/material/guards/README.md b/material/guards/README.md index c2b32a98..384c7f1c 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -360,6 +360,7 @@ export const authGuard: CanActivateFn = () => { }; ``` +In einer realen Anwendung würden wir hier eine eigene UI-Komponente einsetzen, etwa eine Toast-Benachrichtigung oder einen Bestätigungsdialog. Für dieses Beispiel reicht uns hier aber der einfache `alert()`-Aufruf. ### Guard in der Route verwenden In der Routenkonfiguration fügen wir die Eigenschaft `canActivate` hinzu und geben die Guard-Funktion an. From af600c5679723bb0de4cb61353dbe16c599bfe9f Mon Sep 17 00:00:00 2001 From: Ferdinand Malcher Date: Tue, 9 Jun 2026 14:54:23 +0200 Subject: [PATCH 09/14] Update material/guards/README.md --- material/guards/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/material/guards/README.md b/material/guards/README.md index 384c7f1c..2b20d71c 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -304,7 +304,7 @@ const routes: Routes = [ ]; ``` -Ist also im `AuthService` das Property `isAuthenticated` auf `true` gesetzt, wird die erste Route mit der `FeatureComponent` geladen. +Liefert das Signal `isAuthenticated` vom `AuthService` den Wert `true` zurück, wird die erste Route mit der `FeatureComponent` geladen. Steht der Zustand auf `false`, wird die `AnonymousFeatureComponent` angezeigt. Dieses Muster eignet sich hervorragend für Feature Flags, A/B-Testing oder bedingte Routenauswahl. From af87313eab972a3c8f4af5e104ec5dc2a770f0d5 Mon Sep 17 00:00:00 2001 From: Ferdinand Malcher Date: Tue, 9 Jun 2026 14:54:35 +0200 Subject: [PATCH 10/14] Update material/guards/README.md --- material/guards/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/material/guards/README.md b/material/guards/README.md index 2b20d71c..b2e58055 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -226,7 +226,7 @@ export interface HasUnsavedChanges { export const leaveGuard: CanDeactivateFn = (component) => { - if (component.hasUnsavedChanges) { + if (component.hasUnsavedChanges()) { return confirm('Du hast ungespeicherte Änderungen. Möchtest du die Seite wirklich verlassen?'); } return true; From 1c424b2ccf4d2b7ba9e0ff08ed08b33e9efb9db5 Mon Sep 17 00:00:00 2001 From: Ferdinand Malcher Date: Tue, 9 Jun 2026 14:55:46 +0200 Subject: [PATCH 11/14] Apply suggestions from code review Co-authored-by: Johannes Hoppe --- material/guards/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/material/guards/README.md b/material/guards/README.md index b2e58055..be55dafd 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -235,7 +235,7 @@ export const leaveGuard: CanDeactivateFn = ### CanActivateChild: Dürfen Kind-Routen betreten werden? -Ein Guard mit dem Typ `CanActivateChildFn` entscheidet, ob zu den Kindern der betreffenden Route navigiert werden darf. +Ein Guard mit dem Typ `CanActivateChildFn` entscheidet, ob zu den Kind-Routen der betreffenden Route navigiert werden darf. Dieser Guard wird für *alle* Kind-Routen ausgeführt, die mit `children` definiert sind. Das ist besonders nützlich, um eine ganze Gruppe von Routen mit einer einzigen Prüfung abzusichern. @@ -318,14 +318,14 @@ Wollen wir einen Guard nur einmalig nutzen, können wir die Funktion auch direkt const routes: Routes = [ { path: 'bar', - component: MyComponent, + component: BarPage, canDeactivate: [ - (comp: MyComponent) => !comp.hasUnsavedChanges, + (comp: MyComponent) => !comp.hasUnsavedChanges(), ] }, { path: 'secret', - component: SecretComponent, + component: SecretPage, canActivate: [() => inject(AuthService).isAuthenticated()] } ]; From e9ac0916ff0cad0034342a8ab642a46617f6f687 Mon Sep 17 00:00:00 2001 From: Ferdinand Malcher Date: Tue, 9 Jun 2026 14:56:28 +0200 Subject: [PATCH 12/14] Update material/guards/README.md --- material/guards/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/material/guards/README.md b/material/guards/README.md index be55dafd..027a9cb7 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -258,8 +258,8 @@ const routes: Routes = [ path: 'users', canActivateChild: [adminChildGuard], children: [ - { path: 'list', component: UserListComponent }, - { path: 'detail/:id', component: UserDetailComponent }, + { path: 'list', component: UserListPage }, + { path: 'detail/:id', component: UserDetailPage }, ], }, ]; From 6f029d16b1e9001b847d21e34bdbe1947593b6d3 Mon Sep 17 00:00:00 2001 From: Ferdinand Malcher Date: Tue, 9 Jun 2026 14:57:26 +0200 Subject: [PATCH 13/14] Update material/guards/README.md --- material/guards/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/material/guards/README.md b/material/guards/README.md index 027a9cb7..9fed1d15 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -304,8 +304,8 @@ const routes: Routes = [ ]; ``` -Liefert das Signal `isAuthenticated` vom `AuthService` den Wert `true` zurück, wird die erste Route mit der `FeatureComponent` geladen. -Steht der Zustand auf `false`, wird die `AnonymousFeatureComponent` angezeigt. +Liefert das Signal `isAuthenticated` vom `AuthService` den Wert `true` zurück, wird die erste Route mit der `FeaturePage` geladen. +Steht der Zustand auf `false`, wird die `AnonymousFeaturePage` angezeigt. Dieses Muster eignet sich hervorragend für Feature Flags, A/B-Testing oder bedingte Routenauswahl. From 6c51dab4cff96d1da8825ed42ccbd8058e7bbad1 Mon Sep 17 00:00:00 2001 From: Johannes Hoppe Date: Tue, 9 Jun 2026 15:30:06 +0200 Subject: [PATCH 14/14] Update material/guards/README.md Co-authored-by: Ferdinand Malcher --- material/guards/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/material/guards/README.md b/material/guards/README.md index 9fed1d15..546b53ab 100644 --- a/material/guards/README.md +++ b/material/guards/README.md @@ -294,12 +294,12 @@ Entscheidet der Guard, dass die erste Route nicht berücksichtigt wird, wird die const routes: Routes = [ { path: 'myfeature', - component: FeatureComponent, + component: FeaturePage, canMatch: [myMatchGuard] }, { path: 'myfeature', - component: AnonymousFeatureComponent + component: AnonymousFeaturePage } ]; ```