mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(forms): align FormField CVA selection priority with standard forms
Prioritize custom ControlValueAccessor instances over default or built-in accessors when applying the [formField] directive. This is achieved by directly consuming selectValueAccessor from @angular/forms, ensuring absolute alignment with the precedence rules used across standard Angular form directives.
This commit is contained in:
committed by
Andrew Scott
parent
2e9aeea0fe
commit
de56d74da3
@@ -30,6 +30,7 @@ import {
|
||||
NG_VALUE_ACCESSOR,
|
||||
NgControl,
|
||||
ɵFORM_FIELD_PARSE_ERRORS as FORM_FIELD_PARSE_ERRORS,
|
||||
ɵselectValueAccessor as selectValueAccessor,
|
||||
} from '@angular/forms';
|
||||
import {type ValidationError} from '../api/rules';
|
||||
import type {Field, FieldState} from '../api/types';
|
||||
@@ -193,7 +194,18 @@ export class FormField<T> {
|
||||
* @internal
|
||||
*/
|
||||
get controlValueAccessor(): ControlValueAccessor | undefined {
|
||||
return this.controlValueAccessors?.[0] ?? this.interopNgControl?.valueAccessor ?? undefined;
|
||||
if (!this.controlValueAccessors || this.controlValueAccessors.length === 0) {
|
||||
return this.interopNgControl?.valueAccessor ?? undefined;
|
||||
}
|
||||
|
||||
// Rely on the exact logic in `@angular/forms` to pick the accessors with correct priority,
|
||||
// passing our fake `InteropNgControl` to fulfill its first parameter requirement.
|
||||
return (
|
||||
selectValueAccessor(
|
||||
this.interopNgControl as unknown as NgControl,
|
||||
this.controlValueAccessors,
|
||||
) ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,13 @@ import {
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl} from '@angular/forms';
|
||||
import {
|
||||
ControlValueAccessor,
|
||||
DefaultValueAccessor,
|
||||
NG_VALUE_ACCESSOR,
|
||||
NgControl,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms';
|
||||
import {
|
||||
debounce,
|
||||
disabled,
|
||||
@@ -373,6 +379,25 @@ describe('ControlValueAccessor', () => {
|
||||
expect(() => fixture.componentInstance.disabled.set(true)).not.toThrowError(/NG0600/);
|
||||
});
|
||||
|
||||
it('should pick custom CVA over default CVA when both are present', () => {
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
// Import ReactiveFormsModule to provide the non-standalone DefaultValueAccessor directive.
|
||||
// The selector for DefaultValueAccessor matches `[ngDefaultControl]`.
|
||||
imports: [FormField, CustomControl, ReactiveFormsModule],
|
||||
template: `<custom-control [formField]="f" ngDefaultControl />`,
|
||||
})
|
||||
class App {
|
||||
f = form<string>(signal(''));
|
||||
}
|
||||
|
||||
const fixture = act(() => TestBed.createComponent(App));
|
||||
const customControlInstance = fixture.debugElement.children[0].injector.get(CustomControl);
|
||||
|
||||
act(() => fixture.componentInstance.f().value.set('updated'));
|
||||
expect(customControlInstance.writeCount).toBe(2); // 1 initial + 1 update
|
||||
});
|
||||
|
||||
describe('properties', () => {
|
||||
describe('disabled', () => {
|
||||
it('should bind to directive input', () => {
|
||||
|
||||
@@ -393,7 +393,7 @@ export function syncPendingControls(
|
||||
// TODO: vsavkin remove it once https://github.com/angular/angular/issues/3011 is implemented
|
||||
export function selectValueAccessor(
|
||||
dir: NgControl,
|
||||
valueAccessors: ControlValueAccessor[] | null | undefined,
|
||||
valueAccessors: readonly ControlValueAccessor[] | null | undefined,
|
||||
): ControlValueAccessor | null {
|
||||
if (!valueAccessors) return null;
|
||||
|
||||
|
||||
@@ -48,7 +48,11 @@ export {
|
||||
SelectMultipleControlValueAccessor,
|
||||
ɵNgSelectMultipleOption,
|
||||
} from './directives/select_multiple_control_value_accessor';
|
||||
export {SetDisabledStateOption, ɵFORM_FIELD_PARSE_ERRORS} from './directives/shared';
|
||||
export {
|
||||
selectValueAccessor as ɵselectValueAccessor,
|
||||
SetDisabledStateOption,
|
||||
ɵFORM_FIELD_PARSE_ERRORS,
|
||||
} from './directives/shared';
|
||||
export {
|
||||
AsyncValidator,
|
||||
AsyncValidatorFn,
|
||||
|
||||
Reference in New Issue
Block a user