Skip to content

Commit 59f3ce4

Browse files
committed
feat(challenge 48): implement unsaved changes guard and dialog for form navigation
1 parent 543770b commit 59f3ce4

4 files changed

Lines changed: 75 additions & 8 deletions

File tree

apps/forms/48-avoid-losing-form-data/src/app/app.routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Route } from '@angular/router';
2+
import { unsavedChangesGuard } from './guards/unsaved-changes.guard';
23
import { JoinComponent } from './pages/join.component';
34
import { PageComponent } from './pages/page.component';
45

@@ -11,6 +12,7 @@ export const appRoutes: Route[] = [
1112
{
1213
path: 'form',
1314
loadComponent: () => JoinComponent,
15+
canDeactivate: [unsavedChangesGuard],
1416
},
1517
{
1618
path: 'page-1',
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { CanDeactivateFn } from '@angular/router';
2+
import { Observable } from 'rxjs';
3+
4+
export interface CanDeactivateComponent {
5+
canDeactivate: () => boolean | Observable<boolean>;
6+
}
7+
8+
export const unsavedChangesGuard: CanDeactivateFn<CanDeactivateComponent> = (
9+
component,
10+
) => {
11+
if (
12+
component.canDeactivate &&
13+
typeof component.canDeactivate === 'function'
14+
) {
15+
return component.canDeactivate();
16+
}
17+
18+
return true;
19+
};
Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,56 @@
1-
import { ChangeDetectionStrategy, Component } from '@angular/core';
1+
import { Dialog } from '@angular/cdk/dialog';
2+
import {
3+
ChangeDetectionStrategy,
4+
Component,
5+
HostListener,
6+
inject,
7+
viewChild,
8+
} from '@angular/core';
9+
import { map, Observable } from 'rxjs';
10+
import { CanDeactivateComponent } from '../guards/unsaved-changes.guard';
11+
import { AlertDialogComponent } from '../ui/dialog.component';
212
import { FormComponent } from '../ui/form.component';
313

414
@Component({
515
imports: [FormComponent],
616
template: `
7-
<section class="mx-auto max-w-screen-sm">
17+
<section class="mx-auto max-w-screen-sm">
818
<div class="rounded-lg bg-white p-8 shadow-lg lg:p-12">
9-
<app-form />
19+
<app-form #form />
1020
</div>
1121
</section>
1222
`,
1323
changeDetection: ChangeDetectionStrategy.OnPush,
1424
})
15-
export class JoinComponent {}
25+
export class JoinComponent implements CanDeactivateComponent {
26+
dialog = inject(Dialog);
27+
formComponentRef = viewChild(FormComponent);
28+
29+
@HostListener('window:beforeunload', ['$event'])
30+
beforeUnloadHandler(event: BeforeUnloadEvent) {
31+
const form = this.formComponentRef()?.form;
32+
if (form && form.dirty) {
33+
event.preventDefault();
34+
}
35+
}
36+
37+
canDeactivate(): Observable<boolean> | boolean {
38+
const form = this.formComponentRef()?.form;
39+
if (!form || !form.dirty) {
40+
return true;
41+
}
42+
43+
const dialogRef = this.dialog.open(AlertDialogComponent, {
44+
role: 'alertdialog',
45+
ariaDescribedBy: 'dialog-description',
46+
ariaLabelledBy: 'dialog-title',
47+
ariaModal: true,
48+
});
49+
50+
return dialogRef.closed.pipe(
51+
map((canLeave) => {
52+
return canLeave === true;
53+
}),
54+
);
55+
}
56+
}

apps/forms/48-avoid-losing-form-data/src/app/ui/dialog.component.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { ChangeDetectionStrategy, Component } from '@angular/core';
1+
import { DialogRef } from '@angular/cdk/dialog';
2+
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
23

34
// NOTE : this is just the dialog content, you need to implement dialog logic
45

@@ -13,17 +14,21 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
1314
1415
<div class="mt-4 flex gap-2">
1516
<button
16-
class="inline-flex items-center gap-2 rounded-lg bg-red-600 px-4 py-2 text-white hover:bg-red-700">
17+
class="inline-flex items-center gap-2 rounded-lg bg-red-600 px-4 py-2 text-white hover:bg-red-700"
18+
(click)="dialogRef.close(true)">
1719
Yes continue
1820
</button>
1921
2022
<button
21-
class="block rounded-lg px-4 py-2 text-gray-700 transition hover:bg-gray-50">
23+
class="block rounded-lg px-4 py-2 text-gray-700 transition hover:bg-gray-50"
24+
(click)="dialogRef.close(false)">
2225
Stay on page
2326
</button>
2427
</div>
2528
</div>
2629
`,
2730
changeDetection: ChangeDetectionStrategy.OnPush,
2831
})
29-
export class AlertDialogComponent {}
32+
export class AlertDialogComponent {
33+
dialogRef = inject(DialogRef<boolean, AlertDialogComponent>);
34+
}

0 commit comments

Comments
 (0)