pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/angular/angular/pull/67900/files

"https://github.githubassets.com/assets/primer-primitives-10bf9dd67e3d70bd.css" /> feat(forms): add parseErrors to ControlValueAccessor for Signal Forms by alxhub · Pull Request #67900 · angular/angular · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions goldens/public-api/forms/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { OnChanges } from '@angular/core';
import { OnDestroy } from '@angular/core';
import { OnInit } from '@angular/core';
import { Renderer2 } from '@angular/core';
import { Signal } from '@angular/core';
import { SimpleChanges } from '@angular/core';
import { Version } from '@angular/core';
import { ɵControlDirectiveHost } from '@angular/core';
Expand Down Expand Up @@ -245,6 +246,9 @@ export abstract class ControlEvent<T = any> {

// @public
export interface ControlValueAccessor {
readonly parseErrors?: Signal<ReadonlyArray<{
readonly kind: string;
}>> | undefined;
registerOnChange(fn: any): void;
registerOnTouched(fn: any): void;
setDisabledState?(isDisabled: boolean): void;
Expand Down
5 changes: 5 additions & 0 deletions packages/forms/signals/src/directive/control_cva.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export function cvaControlCreate(
parent.state().controlValue.set(value as any),
);
parent.controlValueAccessor!.registerOnTouched(() => parent.state().markAsTouched());

if (parent.controlValueAccessor!.parseErrors) {
parent.parseErrorsSource.set(parent.controlValueAccessor!.parseErrors);
}

parent.registerAsBinding();

const bindings = createBindings<ControlBindingKey | 'controlValue'>();
Expand Down
3 changes: 2 additions & 1 deletion packages/forms/signals/src/directive/form_field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ export class FormField<T> {
private readonly config = inject(SIGNAL_FORMS_CONFIG, {optional: true});
private readonly validityMonitor = inject(InputValidityMonitor);

private readonly parseErrorsSource = signal<
/** @internal */
readonly parseErrorsSource = signal<
Signal<readonly ValidationError.WithoutFieldTree[]> | undefined
>(undefined);

Expand Down
49 changes: 49 additions & 0 deletions packages/forms/signals/test/web/interop.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,55 @@ describe('ControlValueAccessor', () => {
expect(fixture.componentInstance.f().value()).toBe('typing');
});

it('propagates parse errors from CVA to field', () => {
const parseErrors = signal<ReadonlyArray<{readonly kind: string}>>([]);

@Component({
selector: 'custom-control-with-errors',
template: `<input [value]="value" (input)="onInput($event.target.value)" />`,
providers: [{provide: NG_VALUE_ACCESSOR, useExisting: CustomControlWithErrors, multi: true}],
})
class CustomControlWithErrors implements ControlValueAccessor {
value = '';
parseErrors = parseErrors;

private onChangeFn?: (value: string) => void;

writeValue(newValue: string): void {
this.value = newValue;
}

registerOnChange(fn: (value: string) => void): void {
this.onChangeFn = fn;
}

registerOnTouched(fn: () => void): void {}

onInput(newValue: string) {
this.value = newValue;
this.onChangeFn?.(newValue);
}
}

@Component({
imports: [CustomControlWithErrors, FormField],
template: `<custom-control-with-errors [formField]="f" />`,
})
class TestCmp {
readonly f = form(signal('test'));
}

const fixture = act(() => TestBed.createComponent(TestCmp));
const field = fixture.componentInstance.f;

expect(field().errors()).toEqual([]);

act(() => parseErrors.set([{kind: 'custom-parse'}]));
expect(field().errors()).toEqual([
{kind: 'custom-parse', fieldTree: field, formField: jasmine.any(Object) as any},
]);
});

it('should support debounce', async () => {
const {promise, resolve} = promiseWithResolvers<void>();

Expand Down
17 changes: 16 additions & 1 deletion packages/forms/src/directives/control_value_accessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/

import {Directive, ElementRef, InjectionToken, Renderer2} from '@angular/core';
import {Directive, ElementRef, InjectionToken, Renderer2, type Signal} from '@angular/core';

/**
* @description
Expand All @@ -21,6 +21,21 @@ import {Directive, ElementRef, InjectionToken, Renderer2} from '@angular/core';
* @publicApi
*/
export interface ControlValueAccessor {
/**
* @description
* Exposes parse errors when a UI control is used via Signal Forms.
* Parse errors occur when a user enters a value into a UI control (e.g., a non-date string in a date picker)
* that cannot be parsed into the target model type.
*
* @usageNotes
* ### Expose parse errors
*
* ```ts
* readonly parseErrors = signal<ReadonlyArray<{readonly kind: string}>>([]);
* ```
*/
readonly parseErrors?: Signal<ReadonlyArray<{readonly kind: string}>> | undefined;

/**
* @description
* Writes a new value to the element.
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy