Skip to content

Commit b3063d5

Browse files
authored
Merge pull request #128 from sonusindhu/signal-forms
advanced - example 13
2 parents 0c3caf5 + 68ae329 commit b3063d5

15 files changed

+346
-11
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ Each major example category has its own dedicated README with detailed examples
2323

2424
- [Signal Examples](src/app/examples/signal/README.md) — Core signal patterns and usage (**16 examples**)
2525
- [Linked Signal Examples](src/app/examples/linked-signal/README.md) — Linked signals and computed state (**9 examples**)
26-
- [Defer Block Examples](src/app/examples/defer-block/README.md) — Lazy loading and performance (**11 examples**)
27-
- [Control Flow Examples](src/app/examples/control-flow/README.md)@if, @for, @switch blocks (**7 examples**)
26+
- [Defer Block Examples](src/app/examples/defer-block/README.md) — Lazy loading and performance (**12 examples**)
27+
- [Control Flow Examples](src/app/examples/control-flow/README.md)@if, @for, @switch blocks (**3 examples**)
2828
- [Resource API Examples](src/app/examples/resource-api/README.md) — Data fetching and resource patterns (**9 examples**)
29-
- [Advanced Examples](src/app/examples/advanced/README.md) — Real-world and advanced patterns (**10 examples**, including collaborative list, undo/redo, zoneless change detection demo, and more)
29+
- [Advanced Examples](src/app/examples/advanced/README.md) — Real-world and advanced patterns (**13 examples**, including collaborative list, undo/redo, zoneless change detection demo, modal service, preloading, and custom toggle control)
3030
- [Signal Forms Examples](src/app/examples/signal-form/README.md) — Reactive forms with signals (**6 examples**)
3131

3232
Jump into any category above to explore all the examples and details!

src/app/examples/advanced/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ This section contains advanced examples for Angular signals and related features
1414
| 8 | **Two-way Currency Converter** | Signals, two-way binding, setter function, input, currency conversion | Live USD ($) ↔ EUR (€) converter using signals and two-way binding | [🔗 Demo](https://angular-signal-examples.netlify.app/advanced/example8) |
1515
| 9 | **Undo/Redo State Management** | Signals, undo/redo stack, form state, reactive UI | Implement undo/redo stack for a form using Angular signals | [🔗 Demo](https://angular-signal-examples.netlify.app/advanced/example9) |
1616
| 10 | **Collaborative List (Signals + Storage Event)** | Signals, storage event, RxJS, real-time sync | Real-time collaborative list synced across tabs using signals and storage event | [🔗 Demo](https://angular-signal-examples.netlify.app/advanced/example10) |
17+
| 11 | **Modal Service with ViewContainerRef** | Modal, service, ViewContainerRef, dynamic component | Open a modal using a service and ViewContainerRef. Demonstrates dynamic component creation and service-driven modals. | [🔗 Demo](https://angular-signal-examples.netlify.app/advanced/example11) |
18+
| 12 | **On-Demand Preloading via Service & Custom Strategy** | PreloadingStrategy, service, routing, signals | Preload modules only when triggered (e.g., on hover) using a service and custom PreloadingStrategy | [🔗 Demo](https://angular-signal-examples.netlify.app/advanced/example12) |
19+
| 13 | **Custom Toggle Form Control (ControlValueAccessor)** | ControlValueAccessor, forms, signals, reactive & template-driven, disabled state | Fully reactive, typed, and form-friendly custom toggle control. Works with both reactive and template-driven forms, supports disabled state. | [🔗 Demo](https://angular-signal-examples.netlify.app/advanced/example13) |

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,10 @@ const ADVANCED_ROUTES: Route[] = [
6565
path: 'example12',
6666
loadChildren: () => import('./example12/example12-routings'),
6767
},
68+
{
69+
path: 'example13',
70+
loadComponent: () =>
71+
import('./example13/example13.component').then(x => x.AdvancedExample13Component),
72+
},
6873
]
6974
export default ADVANCED_ROUTES;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,9 @@ export const ADVANCED_EXAMPLES: ExampleModel[] = [
6161
content: 'Preload modules only when triggered (e.g., on hover) using a service and custom PreloadingStrategy. Demonstrates bandwidth savings and modern Angular routing.',
6262
routerLink: 'example12',
6363
},
64+
{
65+
title: 'Custom Toggle Form Control (ControlValueAccessor)',
66+
content: 'A fully reactive, typed, and form-friendly custom toggle control using ControlValueAccessor. Works with both reactive and template-driven forms.',
67+
routerLink: 'example13',
68+
},
6469
];

src/app/examples/advanced/example10/example10.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div class="flex flex-wrap gap-2 items-center justify-start mt-4 mb-4">
33
<button mat-stroked-button color="primary" routerLink="/advanced">Go to List</button>
44
<button mat-stroked-button color="primary" [routerLink]="'/advanced/example9'">Prev</button>
5-
<button mat-stroked-button color="primary" disabled>Next</button>
5+
<button mat-stroked-button color="primary" [routerLink]="'/advanced/example11'">Next</button>
66
</div>
77

88
<mat-tab-group>

src/app/examples/advanced/example12/user-list/user-list.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<div class="flex flex-wrap gap-2 items-center justify-start mt-4 mb-4">
44
<button mat-stroked-button color="primary" routerLink="/advanced">Go to List</button>
55
<button mat-stroked-button color="primary" [routerLink]="'/advanced/example11'">Prev</button>
6-
<button mat-stroked-button color="primary" disabled>Next</button>
6+
<button mat-stroked-button color="primary" [routerLink]="'/advanced/example13'">Next</button>
77
</div>
88

99
<mat-tab-group>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<div class="example-container">
2+
<div class="flex flex-wrap gap-2 items-center justify-start mt-4 mb-4">
3+
<button mat-stroked-button color="primary" routerLink="/advanced">Go to List</button>
4+
<button mat-stroked-button color="primary" [routerLink]="'/advanced/example12'">Prev</button>
5+
<button mat-stroked-button color="primary" disabled>Next</button>
6+
</div>
7+
8+
<mat-tab-group>
9+
<mat-tab label="Demo">
10+
<div class="content-area">
11+
<h2 class="text-xl font-bold mb-2">Example 13: Custom Toggle Form Control</h2>
12+
<p class="mb-4 text-gray-700">This example demonstrates a fully reactive, typed, and form-friendly custom toggle control using ControlValueAccessor.</p>
13+
</div>
14+
<div class="demo-section">
15+
<h3 class="text-lg font-semibold mb-2">Basic Usage</h3>
16+
<mat-card class="p-4 bg-white rounded-lg shadow">
17+
<mat-card-content>
18+
<div class="controls flex flex-col gap-6">
19+
<div class="border-b pb-4 mb-4">
20+
<p class="font-medium mb-2">Reactive Form</p>
21+
<form [formGroup]="toggleControl" class="flex items-center gap-4">
22+
<div>
23+
<label for="toggle" class="mr-2 font-semibold">Toggle:</label>
24+
<app-toggle formControlName="toggle" [isDisabled]="reactiveDisabled" class="mx-2"></app-toggle>
25+
</div>
26+
<div class="ml-4 text-sm text-gray-600">
27+
<p>Current Value: {{ toggleControl.value | json }}</p>
28+
</div>
29+
<button type="button" class="ml-4 px-3 py-1 rounded bg-gray-200 hover:bg-gray-300 text-sm font-medium" (click)="toggleReactiveDisabled()">
30+
{{ reactiveDisabled ? 'Enable' : 'Disable' }} Toggle
31+
</button>
32+
</form>
33+
</div>
34+
<div>
35+
<p class="font-medium mb-2">Toggle with ngModel</p>
36+
<div class="flex items-center gap-4">
37+
<div>
38+
<label for="toggle" class="mr-2 font-semibold">Toggle:</label>
39+
<app-toggle [(ngModel)]="toggleModel" [isDisabled]="ngModelDisabled" class="mx-2"></app-toggle>
40+
</div>
41+
<div class="ml-4 text-sm text-gray-600">
42+
<p>Current Value: {{ toggleModel }}</p>
43+
</div>
44+
<button type="button" class="ml-4 px-3 py-1 rounded bg-gray-200 hover:bg-gray-300 text-sm font-medium" (click)="toggleNgModelDisabled()">
45+
{{ ngModelDisabled ? 'Enable' : 'Disable' }} Toggle
46+
</button>
47+
</div>
48+
</div>
49+
</div>
50+
</mat-card-content>
51+
</mat-card>
52+
</div>
53+
</mat-tab>
54+
<mat-tab label="TS">
55+
<markdown src="assets/examples/advanced/example13/example13.component.ts.md"></markdown>
56+
</mat-tab>
57+
<mat-tab label="Toggle Control">
58+
<markdown src="assets/examples/advanced/example13/toggle-control.component.ts.md"></markdown>
59+
</mat-tab>
60+
<mat-tab label="HTML">
61+
<markdown src="assets/examples/advanced/example13/example13.component.html.md"></markdown>
62+
</mat-tab>
63+
<mat-tab label="SCSS">
64+
<markdown src="assets/examples/advanced/example13/example13.component.scss.md"></markdown>
65+
</mat-tab>
66+
</mat-tab-group>
67+
</div>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:host{
2+
display: contents;
3+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Component } from '@angular/core';
2+
import { ReactiveFormsModule, FormsModule, FormGroup, FormControl } from '@angular/forms';
3+
import { ToggleComponent } from './toggle-control.component';
4+
import { MatCardModule } from '@angular/material/card';
5+
import { MatTabsModule } from '@angular/material/tabs';
6+
import { MatButtonModule } from '@angular/material/button';
7+
import { MarkdownComponent } from 'ngx-markdown';
8+
import { JsonPipe, NgClass } from '@angular/common';
9+
import { RouterLink } from '@angular/router';
10+
import { CommonModule } from '@angular/common';
11+
12+
@Component({
13+
selector: 'app-advanced-example13',
14+
standalone: true,
15+
imports: [
16+
CommonModule,
17+
RouterLink,
18+
ReactiveFormsModule,
19+
FormsModule,
20+
ToggleComponent,
21+
MatCardModule,
22+
MatTabsModule,
23+
MatButtonModule,
24+
MarkdownComponent,
25+
JsonPipe,
26+
NgClass
27+
],
28+
templateUrl: './example13.component.html',
29+
styleUrl: './example13.component.scss'
30+
})
31+
export class AdvancedExample13Component {
32+
toggleControl = new FormGroup({
33+
toggle: new FormControl<boolean>(false)
34+
});
35+
toggleModel = false;
36+
37+
reactiveDisabled = false;
38+
ngModelDisabled = false;
39+
40+
toggleReactiveDisabled() {
41+
this.reactiveDisabled = !this.reactiveDisabled;
42+
if (this.reactiveDisabled) {
43+
this.toggleControl.disable();
44+
} else {
45+
this.toggleControl.enable();
46+
}
47+
}
48+
49+
toggleNgModelDisabled() {
50+
this.ngModelDisabled = !this.ngModelDisabled;
51+
}
52+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Component, forwardRef, Input } from '@angular/core';
2+
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
3+
import { NgClass } from '@angular/common';
4+
5+
@Component({
6+
selector: 'app-toggle',
7+
standalone: true,
8+
imports: [NgClass],
9+
template: `
10+
<div
11+
(click)="toggle()"
12+
[class.on]="value"
13+
[class.disabled]="isDisabled"
14+
class="cursor-pointer select-none px-6 py-2 rounded-full border-2 transition-colors duration-200 font-semibold text-center w-24"
15+
[ngClass]="{
16+
'bg-blue-600 text-white border-blue-600': value && !isDisabled,
17+
'bg-gray-200 text-gray-700 border-gray-400': !value && !isDisabled,
18+
'opacity-50 cursor-not-allowed': isDisabled
19+
}"
20+
tabindex="0"
21+
role="switch"
22+
[attr.aria-checked]="value"
23+
[attr.aria-disabled]="isDisabled"
24+
>
25+
{{ value ? 'ON' : 'OFF' }}
26+
</div>
27+
`,
28+
providers: [{
29+
provide: NG_VALUE_ACCESSOR,
30+
useExisting: forwardRef(() => ToggleComponent),
31+
multi: true
32+
}]
33+
})
34+
export class ToggleComponent implements ControlValueAccessor {
35+
@Input() isDisabled = false;
36+
value = false;
37+
38+
private onChange = (value: boolean) => {};
39+
private onTouched = () => {};
40+
41+
toggle() {
42+
if (this.isDisabled) return;
43+
this.value = !this.value;
44+
this.onChange(this.value);
45+
this.onTouched();
46+
}
47+
48+
writeValue(value: boolean) { this.value = value; }
49+
registerOnChange(fn: (value: boolean) => void) { this.onChange = fn; }
50+
registerOnTouched(fn: () => void) { this.onTouched = fn; }
51+
setDisabledState(isDisabled: boolean) { this.isDisabled = isDisabled; }
52+
}

0 commit comments

Comments
 (0)