Skip to content

Commit 9c81a3a

Browse files
committed
feat: add Example 13 with custom toggle form control and update related documentation
1 parent 2913e5c commit 9c81a3a

File tree

5 files changed

+172
-6
lines changed

5 files changed

+172
-6
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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="Code">
55+
<markdown src="assets/examples/advanced/example13/example13.component.ts.md"></markdown>
56+
<markdown src="assets/examples/advanced/example13/toggle-control.component.ts.md"></markdown>
57+
</mat-tab>
58+
</mat-tab-group>
59+
</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+
}

src/app/home/home.component.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ <h1 class="text-4xl sm:text-5xl font-bold leading-tight mb-6">
3030
<span class="text-xs uppercase tracking-wider text-gray-500">Examples</span>
3131
</div>
3232
<div class="text-center">
33-
<span class="block text-2xl font-semibold text-gray-900">6</span>
33+
<span class="block text-2xl font-semibold text-gray-900">7</span>
3434
<span class="text-xs uppercase tracking-wider text-gray-500">Categories</span>
3535
</div>
3636
<div class="text-center">
@@ -117,7 +117,7 @@ <h3 class="feature-title text-xl font-semibold text-gray-900 mb-2 text-center">S
117117
<mat-icon class="text-3xl text-gray-500">account_tree</mat-icon>
118118
</div>
119119
<h3 class="feature-title text-xl font-semibold text-gray-900 mb-2 text-center">LinkedSignal Examples</h3>
120-
<p class="feature-description text-gray-600 mb-4 text-center">Advanced source tracking with automatic updates and complex business logic for professional applications</p>
120+
<p class="feature-description text-gray-600 mb-4 text-center">Advanced source tracking with automatic updates and complex business logic for professional applications (9 examples)</p>
121121
<button class="feature-button w-full bg-gray-100 text-gray-800 border border-gray-200 rounded-lg py-2 font-medium hover:bg-gray-200 transition">Discover LinkedSignals</button>
122122
</div>
123123
<div class="feature-card relative cursor-pointer bg-white border border-gray-200 rounded-xl p-6 shadow-sm hover:shadow-lg transition" [routerLink]="['/resource-api']">
@@ -126,7 +126,7 @@ <h3 class="feature-title text-xl font-semibold text-gray-900 mb-2 text-center">L
126126
<mat-icon class="text-3xl text-gray-500">cloud_sync</mat-icon>
127127
</div>
128128
<h3 class="feature-title text-xl font-semibold text-gray-900 mb-2 text-center">Resource API</h3>
129-
<p class="feature-description text-gray-600 mb-4 text-center">Modern data fetching with reactive state management, loading states, and error handling patterns</p>
129+
<p class="feature-description text-gray-600 mb-4 text-center">Modern data fetching with reactive state management, loading states, and error handling patterns (9 examples)</p>
130130
<button class="feature-button w-full bg-gray-100 text-gray-800 border border-gray-200 rounded-lg py-2 font-medium hover:bg-gray-200 transition">Learn Resource API</button>
131131
</div>
132132
<div class="feature-card relative cursor-pointer bg-white border border-gray-200 rounded-xl p-6 shadow-sm hover:shadow-lg transition" [routerLink]="['/defer-block']">
@@ -135,7 +135,7 @@ <h3 class="feature-title text-xl font-semibold text-gray-900 mb-2 text-center">R
135135
<mat-icon class="text-3xl text-gray-500">schedule</mat-icon>
136136
</div>
137137
<h3 class="feature-title text-xl font-semibold text-gray-900 mb-2 text-center">Defer Blocks</h3>
138-
<p class="feature-description text-gray-600 mb-4 text-center">Performance optimization through lazy loading and intelligent deferred rendering techniques</p>
138+
<p class="feature-description text-gray-600 mb-4 text-center">Performance optimization through lazy loading and intelligent deferred rendering techniques (12 examples)</p>
139139
<button class="feature-button w-full bg-gray-100 text-gray-800 border border-gray-200 rounded-lg py-2 font-medium hover:bg-gray-200 transition">Explore Defer Blocks</button>
140140
</div>
141141
<div class="feature-card relative cursor-pointer bg-white border border-gray-200 rounded-xl p-6 shadow-sm hover:shadow-lg transition" [routerLink]="['/control-flow']">
@@ -144,7 +144,7 @@ <h3 class="feature-title text-xl font-semibold text-gray-900 mb-2 text-center">D
144144
<mat-icon class="text-3xl text-gray-500">alt_route</mat-icon>
145145
</div>
146146
<h3 class="feature-title text-xl font-semibold text-gray-900 mb-2 text-center">Control Flow</h3>
147-
<p class="feature-description text-gray-600 mb-4 text-center">Explore Angular's new control flow blocks (&#64;if, &#64;for, &#64;switch) with interactive, signal-powered demos and real-world patterns</p>
147+
<p class="feature-description text-gray-600 mb-4 text-center">Explore Angular's new control flow blocks (&#64;if, &#64;for, &#64;switch) with interactive, signal-powered demos and real-world patterns (3 examples)</p>
148148
<button class="feature-button w-full bg-gray-100 text-gray-800 border border-gray-200 rounded-lg py-2 font-medium hover:bg-gray-200 transition">Try Control Flow</button>
149149
</div>
150150
<div class="feature-card relative cursor-pointer bg-white border border-gray-200 rounded-xl p-6 shadow-sm hover:shadow-lg transition" [routerLink]="['/signal-form']">
@@ -153,7 +153,7 @@ <h3 class="feature-title text-xl font-semibold text-gray-900 mb-2 text-center">C
153153
<mat-icon class="text-3xl text-gray-500">assignment</mat-icon>
154154
</div>
155155
<h3 class="feature-title text-xl font-semibold text-gray-900 mb-2 text-center">Signal Forms</h3>
156-
<p class="feature-description text-gray-600 mb-4 text-center">Reactive forms with Angular signals: state management, validation, and custom logic. 3 practical examples.</p>
156+
<p class="feature-description text-gray-600 mb-4 text-center">Reactive forms with Angular signals: state management, validation, and custom logic. 6 practical examples.</p>
157157
<button class="feature-button w-full bg-gray-100 text-gray-800 border border-gray-200 rounded-lg py-2 font-medium hover:bg-gray-200 transition">Explore Signal Forms</button>
158158
</div>
159159
</div>

0 commit comments

Comments
 (0)