Skip to content

Commit 0c3caf5

Browse files
authored
Merge pull request #127 from sonusindhu/signal-forms
Signal forms - example 11, 12
2 parents 261801d + d82c56a commit 0c3caf5

31 files changed

+758
-14
lines changed
File renamed without changes.
File renamed without changes.
File renamed without changes.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { PreloadingStrategy, Route } from '@angular/router';
2+
import { Injectable } from '@angular/core';
3+
import { Observable, Subject } from 'rxjs';
4+
import { switchMap } from 'rxjs/operators';
5+
6+
@Injectable({ providedIn: 'root' })
7+
export class CustomPreloadingStrategy implements PreloadingStrategy {
8+
private preloadTriggers: { [key: string]: Subject<void> } = {};
9+
10+
preload(route: Route, load: () => Observable<any>): Observable<any> {
11+
console.log('CustomPreloadingStrategy: Checking route', route.data, route.path);
12+
if (route.data && route.data['preloadOnDemand']) {
13+
if (!this.preloadTriggers[route.path!]) {
14+
this.preloadTriggers[route.path!] = new Subject<void>();
15+
}
16+
return this.preloadTriggers[route.path!].asObservable().pipe(
17+
// When triggered, start loading
18+
switchMap(() => load())
19+
);
20+
}
21+
// Default: do not preload
22+
return new Observable<any>();
23+
}
24+
25+
triggerPreload(path: string) {
26+
if (this.preloadTriggers[path]) {
27+
this.preloadTriggers[path].next();
28+
}
29+
}
30+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Injectable } from '@angular/core';
2+
import { CustomPreloadingStrategy } from './custom-preloading.strategy';
3+
4+
@Injectable({ providedIn: 'root' })
5+
export class PreloadService {
6+
constructor(private strategy: CustomPreloadingStrategy) {}
7+
8+
preload(path: string) {
9+
this.strategy.triggerPreload(path);
10+
}
11+
}

src/app/examples/advanced/advanced-routings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,9 @@ const ADVANCED_ROUTES: Route[] = [
6161
loadComponent: () =>
6262
import('./example11/example11.component').then(x => x.Example11Component),
6363
},
64+
{
65+
path: 'example12',
66+
loadChildren: () => import('./example12/example12-routings'),
67+
},
6468
]
6569
export default ADVANCED_ROUTES;

src/app/examples/advanced/advanced.const.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,14 @@ export const ADVANCED_EXAMPLES: ExampleModel[] = [
5151
content: 'A real-time collaborative list using Angular signals and the browser storage event. Add, edit, or remove items and see changes sync instantly across tabs/windows—no backend required.',
5252
routerLink: 'example10',
5353
},
54+
{
55+
title: 'Modal Service with ViewContainerRef',
56+
content: 'Open a modal using a service and ViewContainerRef. Demonstrates dynamic component creation and service-driven modals.',
57+
routerLink: 'example11',
58+
},
59+
{
60+
title: 'On-Demand Preloading via Service & Custom Strategy',
61+
content: 'Preload modules only when triggered (e.g., on hover) using a service and custom PreloadingStrategy. Demonstrates bandwidth savings and modern Angular routing.',
62+
routerLink: 'example12',
63+
},
5464
];

src/app/examples/advanced/example11/modal.component.ts renamed to src/app/examples/advanced/example11/custom-model/modal.component.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component } from '@angular/core';
2-
import { ModalRef } from './custom-model/model.ref';
2+
import { ModalRef } from './model.ref';
33

44
@Component({
55
selector: 'app-confirm-modal',
@@ -14,18 +14,41 @@ import { ModalRef } from './custom-model/model.ref';
1414
`,
1515
styles: [`
1616
.backdrop {
17-
position: fixed; inset: 0;
17+
position: fixed;
18+
inset: 0;
1819
background: rgba(0,0,0,0.5);
20+
z-index: 1000;
1921
}
2022
.modal {
2123
position: fixed;
2224
top: 50%; left: 50%;
2325
transform: translate(-50%, -50%);
24-
background: white;
25-
padding: 1rem;
26-
border-radius: 8px;
27-
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
28-
z-index: 1000;
26+
background: #fff;
27+
padding: 2rem;
28+
border-radius: 10px;
29+
box-shadow: 0 4px 16px rgba(0,0,0,0.25);
30+
min-width: 320px;
31+
min-height: 120px;
32+
z-index: 1001;
33+
display: flex;
34+
flex-direction: column;
35+
align-items: center;
36+
gap: 1rem;
37+
}
38+
.modal h2 {
39+
margin-bottom: 0.5rem;
40+
font-size: 1.3rem;
41+
font-weight: 600;
42+
}
43+
.modal p {
44+
color: #555;
45+
font-size: 1rem;
46+
margin-bottom: 1rem;
47+
}
48+
.modal button {
49+
min-width: 100px;
50+
font-size: 1rem;
51+
margin: 0 0.5rem;
2952
}
3053
`]
3154
})

src/app/examples/advanced/example11/custom-model/modal.service.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ export class ModalService {
2121
}
2222

2323
open<T extends object>(component: Type<T>, inputs?: Partial<T>): ModalRef<T> {
24+
// Always (re)create the modal container if missing
25+
let existing = document.getElementById('app-modal-container');
26+
if (existing) {
27+
this.modalContainer = existing;
28+
} else {
29+
this.modalContainer = document.createElement('div');
30+
this.modalContainer.id = 'app-modal-container';
31+
document.body.appendChild(this.modalContainer);
32+
}
33+
2434
// 1. Create container
2535
const containerRef = createComponent(ModalContainerComponent, {
2636
environmentInjector: this.appRef.injector as EnvironmentInjector,
@@ -38,11 +48,18 @@ export class ModalService {
3848

3949
// 3. Build ModalRef for inner component
4050
const modalRef = new ModalRef(cmpRef);
41-
42-
// optional: allow modal to close itself
4351
(cmpRef.instance as any).modalRef = modalRef;
4452

45-
// return reference to **inner component**
53+
// Remove modal and container from DOM on close/confirm
54+
const removeModal = () => {
55+
this.appRef.detachView(containerRef.hostView);
56+
if (this.modalContainer.parentNode) {
57+
this.modalContainer.parentNode.removeChild(this.modalContainer);
58+
}
59+
};
60+
modalRef.onClose.subscribe(removeModal);
61+
modalRef.onConfirm.subscribe(removeModal);
62+
4663
return modalRef;
4764
}
4865
}

src/app/examples/advanced/example11/custom-model/model-container.component.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,31 @@ export class ModalContainerComponent implements OnDestroy {
4040
Object.assign(this.childRef.instance, inputs);
4141
}
4242
(this.childRef.instance as any).modalRef = this.modalRef;
43+
44+
// Listen for close and confirm events to destroy modal
45+
if (this.modalRef) {
46+
this.modalRef.onClose.subscribe(() => {
47+
this.removeModal();
48+
});
49+
this.modalRef.onConfirm.subscribe(() => {
50+
this.removeModal();
51+
});
52+
}
4353
}
4454

4555
onBackdropClick() {
4656
this.modalRef.close();
4757
}
4858

59+
removeModal() {
60+
this.childRef?.destroy();
61+
// Remove modal container from DOM
62+
const el = (this.vc.element.nativeElement as HTMLElement).parentElement?.parentElement;
63+
if (el && el.parentElement) {
64+
el.parentElement.removeChild(el);
65+
}
66+
}
67+
4968
ngOnDestroy() {
5069
this.childRef?.destroy();
5170
}

0 commit comments

Comments
 (0)