Skip to content

Commit

Permalink
example: CVA (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
timdeschryver authored Mar 26, 2024
1 parent 6dc1a3f commit 61d2e2f
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 4 deletions.
3 changes: 3 additions & 0 deletions apps/example/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { RouterLink, RouterOutlet } from '@angular/router';
<li>
<a routerLink="multi-page-form">Multipage form</a>
</li>
<li>
<a routerLink="form-with-cva">Form with CVA</a>
</li>
</ul>
</nav>
<router-outlet />
Expand Down
4 changes: 4 additions & 0 deletions apps/example/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ export const routes: Routes = [
path: 'multi-page-form',
loadChildren: () => import('./multi-page-form/multi-page-form.routes'),
},
{
path: 'form-with-cva',
loadComponent: () => import('./form-with-cva/form-with-cva.component'),
},
];
79 changes: 79 additions & 0 deletions apps/example/src/app/form-with-cva/address/address.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
forwardRef, effect
} from '@angular/core';
import {
ControlValueAccessor,
FormsModule,
NG_VALUE_ACCESSOR
} from '@angular/forms';
import { createFormField, createFormGroup, SignalInputDirective, V } from '@ng-signal-forms';
import { Address } from './address';

@Component({
selector: 'app-address',
standalone: true,
imports: [CommonModule, SignalInputDirective, FormsModule],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AddressComponent),
multi: true
}
],
template: `
<div>
<div>
<label>Street</label>
<input type="text" ngModel [formField]="formGroup.controls.street" />
</div>
<div>
<label>Zip</label>
<input type="text" ngModel [formField]="formGroup.controls.zip" />
</div>
<div>
<label>City</label>
<input type="text" ngModel [formField]="formGroup.controls.city" />
</div>
<div>
<label>Country</label>
<input type="text" ngModel [formField]="formGroup.controls.country" />
</div>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AddressComponent
implements ControlValueAccessor {
onChange: any;
onTouched: any;
formGroup = createFormGroup({
street: createFormField(undefined as undefined | string, { validators: [V.minLength(3)] }),
zip: createFormField(undefined as undefined | string, { validators: [V.minLength(3)] }),
city: createFormField(undefined as undefined | string, { validators: [V.minLength(3)] }),
country: createFormField(undefined as undefined | string, { validators: [V.minLength(3)] })
});

change = effect(() => {
this.onChange(this.formGroup.value());
}, { allowSignalWrites: true });

registerOnChange(onChange: any): void {
this.onChange = onChange;
}

registerOnTouched(onTouched: any): void {
this.onTouched = onTouched;
}

writeValue(obj: Address): void {
// TODO: obj should receive values from parent
if (obj) {
this.formGroup.controls.street.value.set(obj.street);
this.formGroup.controls.zip.value.set(obj.zip);
this.formGroup.controls.city.value.set(obj.city);
this.formGroup.controls.country.value.set(obj.country);
}
}
}
6 changes: 6 additions & 0 deletions apps/example/src/app/form-with-cva/address/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Address {
street?: string;
zip?: string;
city?: string;
country?: string;
}
94 changes: 94 additions & 0 deletions apps/example/src/app/form-with-cva/form-with-cva.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { JsonPipe, NgFor, NgIf } from '@angular/common';
import { Component, effect } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
createFormGroup,
SignalInputDebounceDirective,
SignalInputDirective,
SignalInputErrorDirective,
withErrorComponent
} from '@ng-signal-forms';
import { CustomErrorComponent } from '../custom-input-error.component';
import { AddressComponent } from './address/address.component';

@Component({
selector: 'app-basic-form',
template: `
<div class="container">
<div>
<div>
<label>Name</label>
<input ngModel [formField]="form.controls.name" />
</div>
<div>
<label>Age</label>
<input type="number" ngModel [formField]="form.controls.age" />
</div>
<div>
<app-address ngModel [formField]="form.controls.address" />
</div>
</div>
<div>
<button (click)="reset()">Reset form</button>
<button (click)="prefill()">Prefill form</button>
<h3>States</h3>
<pre
>{{
{
state: form.state(),
dirtyState: form.dirtyState(),
touchedState: form.touchedState(),
valid: form.valid()
} | json
}}
</pre>
<h3>Value</h3>
<pre>{{ form.value() | json }}</pre>
<h3>Errors</h3>
<pre>{{ form.errorsArray() | json }}</pre>
</div>
</div>
`,
standalone: true,
imports: [
JsonPipe,
FormsModule,
SignalInputDirective,
SignalInputErrorDirective,
NgIf,
NgFor,
SignalInputDebounceDirective,
AddressComponent,
],
providers: [withErrorComponent(CustomErrorComponent)],
})
export default class FormWithCvaComponent {
// TODO: type of address should be Address | null
form = createFormGroup({
name: 'Alice',
age: null as number | null,
// TODO: this should a form group so the initial value will be set in the form
// but if we make this a group now, then the user input is not emitted back to the form
address: { city: 'Vienna'} as any
});

formChanged = effect(() => {
console.log('form changed:', this.form.value());
});

reset() {
this.form.reset();
}

prefill() {
// TODO: improve this API to set form groups
this.form.controls.age.value.set(42);
this.form.controls.name.value.set('Bob');
}
}
8 changes: 4 additions & 4 deletions packages/platform/src/lib/signal-input.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import { SIGNAL_INPUT_MODIFIER, SignalInputModifier } from "./signal-input-modif
'[class.ng-dirty]': 'this.formField?.dirtyState() === "DIRTY"',
'[class.ng-touched]': 'this.formField?.touchedState() === "TOUCHED"',
'[class.ng-untouched]': 'this.formField?.touchedState() === "UNTOUCHED"',
'[attr.disabled]': '!propagateState ? undefined : this.formField?.disabled() ? true : undefined',
'[attr.readonly]': '!propagateState ? undefined : this.formField?.readOnly() ? true : undefined',
'[attr.disabled]': '!propagateState ? undefined : this.formField?.disabled?.() ? true : undefined',
'[attr.readonly]': '!propagateState ? undefined : this.formField?.readOnly?.() ? true : undefined',
},
})
export class SignalInputDirective implements OnInit {
Expand All @@ -30,7 +30,7 @@ export class SignalInputDirective implements OnInit {
onModelChange(value: unknown) {
if (this.modifiers && this.modifiers.length === 1) {
this.modifiers[0].onModelChange(value);
} else if (this.formField) {
} else if (this.formField && this.formField.value.set) {
this.formField.value.set(value);
}
}
Expand Down Expand Up @@ -67,6 +67,6 @@ export class SignalInputDirective implements OnInit {
emitModelToViewChange: true,
}));

this.formField?.registerOnReset(value => this.model.control.setValue(value))
this.formField?.registerOnReset?.(value => this.model.control.setValue(value))
}
}

0 comments on commit 61d2e2f

Please sign in to comment.