diff --git a/goldens/public-api/forms/index.md b/goldens/public-api/forms/index.md index ced3a85904b41..35987fe3583a0 100644 --- a/goldens/public-api/forms/index.md +++ b/goldens/public-api/forms/index.md @@ -340,7 +340,7 @@ export const FormControl: ɵFormControlCtor; // @public export class FormControlDirective extends NgControl implements OnChanges, OnDestroy { - constructor(validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[], valueAccessors: ControlValueAccessor[], _ngModelWarningConfig: string | null); + constructor(validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[], valueAccessors: ControlValueAccessor[], _ngModelWarningConfig: string | null, callSetDisabledState?: SetDisabledStateOption | null | undefined); get control(): FormControl; form: FormControl; set isDisabled(isDisabled: boolean); @@ -358,7 +358,7 @@ export class FormControlDirective extends NgControl implements OnChanges, OnDest // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵfac: i0.ɵɵFactoryDeclaration; } // @public @@ -466,7 +466,7 @@ export class FormGroup; // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵfac: i0.ɵɵFactoryDeclaration; } // @public @@ -551,6 +551,9 @@ export interface FormRecord { // @public export class FormsModule { + static withConfig(opts: { + callSetDisabledState?: SetDisabledStateOption; + }): ModuleWithProviders; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; // (undocumented) @@ -631,7 +634,7 @@ export class NgControlStatusGroup extends AbstractControlStatus { // @public export class NgForm extends ControlContainer implements Form, AfterViewInit { - constructor(validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[]); + constructor(validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[], callSetDisabledState?: SetDisabledStateOption | null | undefined); addControl(dir: NgModel): void; addFormGroup(dir: NgModelGroup): void; get control(): FormGroup; @@ -662,12 +665,12 @@ export class NgForm extends ControlContainer implements Form, AfterViewInit { // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵfac: i0.ɵɵFactoryDeclaration; } // @public export class NgModel extends NgControl implements OnChanges, OnDestroy { - constructor(parent: ControlContainer, validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[], valueAccessors: ControlValueAccessor[], _changeDetectorRef?: ChangeDetectorRef | null | undefined); + constructor(parent: ControlContainer, validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[], valueAccessors: ControlValueAccessor[], _changeDetectorRef?: ChangeDetectorRef | null | undefined, callSetDisabledState?: SetDisabledStateOption | null | undefined); // (undocumented) readonly control: FormControl; get formDirective(): any; @@ -692,7 +695,7 @@ export class NgModel extends NgControl implements OnChanges, OnDestroy { // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵfac: i0.ɵɵFactoryDeclaration; } // @public @@ -787,7 +790,8 @@ export class RangeValueAccessor extends BuiltInControlValueAccessor implements C // @public export class ReactiveFormsModule { static withConfig(opts: { - warnOnNgModelWithFormControl: 'never' | 'once' | 'always'; + warnOnNgModelWithFormControl?: 'never' | 'once' | 'always'; + callSetDisabledState?: SetDisabledStateOption; }): ModuleWithProviders; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; diff --git a/goldens/size-tracking/integration-payloads.json b/goldens/size-tracking/integration-payloads.json index 4997bec8ca1c4..824cf109b8775 100644 --- a/goldens/size-tracking/integration-payloads.json +++ b/goldens/size-tracking/integration-payloads.json @@ -41,7 +41,7 @@ "forms": { "uncompressed": { "runtime": 1060, - "main": 155712, + "main": 157903, "polyfills": 33915 } }, @@ -68,4 +68,4 @@ "bundle": 1214857 } } -} +} \ No newline at end of file diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index cc34b5820d893..41332407477bc 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -56,6 +56,9 @@ { "name": "BuiltInControlValueAccessor" }, + { + "name": "CALL_SET_DISABLED_STATE" + }, { "name": "CHECKBOX_VALUE_ACCESSOR" }, diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index 24014002c6e70..c5067f654ff01 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -59,6 +59,9 @@ { "name": "BuiltInControlValueAccessor" }, + { + "name": "CALL_SET_DISABLED_STATE" + }, { "name": "CHECKBOX_VALUE_ACCESSOR" }, diff --git a/packages/forms/src/directives.ts b/packages/forms/src/directives.ts index 92af71a67ba17..59cfa802dc815 100644 --- a/packages/forms/src/directives.ts +++ b/packages/forms/src/directives.ts @@ -43,6 +43,7 @@ export {FormGroupDirective} from './directives/reactive_directives/form_group_di export {FormArrayName, FormGroupName} from './directives/reactive_directives/form_group_name'; export {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor'; export {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor'; +export {CALL_SET_DISABLED_STATE} from './directives/shared'; export const SHARED_FORM_DIRECTIVES: Type[] = [ NgNoValidate, diff --git a/packages/forms/src/directives/ng_form.ts b/packages/forms/src/directives/ng_form.ts index ae66484e01815..734a71c815f7b 100644 --- a/packages/forms/src/directives/ng_form.ts +++ b/packages/forms/src/directives/ng_form.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AfterViewInit, Directive, EventEmitter, forwardRef, Inject, Input, Optional, Self} from '@angular/core'; +import {AfterViewInit, Directive, EventEmitter, forwardRef, inject, Inject, Input, Optional, Self} from '@angular/core'; import {AbstractControl, FormHooks} from '../model/abstract_model'; import {FormControl} from '../model/form_control'; @@ -18,7 +18,7 @@ import {Form} from './form_interface'; import {NgControl} from './ng_control'; import {NgModel} from './ng_model'; import {NgModelGroup} from './ng_model_group'; -import {setUpControl, setUpFormContainer, syncPendingControls} from './shared'; +import {CALL_SET_DISABLED_STATE, SetDisabledStateOption, setUpControl, setUpFormContainer, syncPendingControls} from './shared'; import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators'; export const formDirectiveProvider: any = { @@ -135,7 +135,9 @@ export class NgForm extends ControlContainer implements Form, AfterViewInit { constructor( @Optional() @Self() @Inject(NG_VALIDATORS) validators: (Validator|ValidatorFn)[], @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: - (AsyncValidator|AsyncValidatorFn)[]) { + (AsyncValidator|AsyncValidatorFn)[], + @Optional() @Inject(CALL_SET_DISABLED_STATE) private callSetDisabledState?: + SetDisabledStateOption|null) { super(); this.form = new FormGroup({}, composeValidators(validators), composeAsyncValidators(asyncValidators)); @@ -191,7 +193,7 @@ export class NgForm extends ControlContainer implements Form, AfterViewInit { const container = this._findContainer(dir.path); (dir as {control: FormControl}).control = container.registerControl(dir.name, dir.control); - setUpControl(dir.control, dir); + setUpControl(dir.control, dir, this.callSetDisabledState); dir.control.updateValueAndValidity({emitEvent: false}); this._directives.add(dir); }); diff --git a/packages/forms/src/directives/ng_model.ts b/packages/forms/src/directives/ng_model.ts index f3fab973f26e4..680c6a2b752b8 100644 --- a/packages/forms/src/directives/ng_model.ts +++ b/packages/forms/src/directives/ng_model.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectorRef, Directive, EventEmitter, forwardRef, Host, Inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges, ɵcoerceToBoolean as coerceToBoolean} from '@angular/core'; +import {ChangeDetectorRef, Directive, EventEmitter, forwardRef, Host, inject, Inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges, ɵcoerceToBoolean as coerceToBoolean} from '@angular/core'; import {FormHooks} from '../model/abstract_model'; import {FormControl} from '../model/form_control'; @@ -18,7 +18,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor' import {NgControl} from './ng_control'; import {NgForm} from './ng_form'; import {NgModelGroup} from './ng_model_group'; -import {controlPath, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared'; +import {CALL_SET_DISABLED_STATE, controlPath, isPropertyUpdated, selectValueAccessor, SetDisabledStateOption, setUpControl} from './shared'; import {formGroupNameException, missingNameException, modelParentException} from './template_driven_errors'; import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators'; @@ -210,7 +210,9 @@ export class NgModel extends NgControl implements OnChanges, OnDestroy { @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: (AsyncValidator|AsyncValidatorFn)[], @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[], - @Optional() @Inject(ChangeDetectorRef) private _changeDetectorRef?: ChangeDetectorRef|null) { + @Optional() @Inject(ChangeDetectorRef) private _changeDetectorRef?: ChangeDetectorRef|null, + @Optional() @Inject(CALL_SET_DISABLED_STATE) private callSetDisabledState?: + SetDisabledStateOption|null) { super(); this._parent = parent; this._setValidators(validators); @@ -295,7 +297,7 @@ export class NgModel extends NgControl implements OnChanges, OnDestroy { } private _setUpStandalone(): void { - setUpControl(this.control, this); + setUpControl(this.control, this, this.callSetDisabledState); this.control.updateValueAndValidity({emitEvent: false}); } diff --git a/packages/forms/src/directives/reactive_directives/form_control_directive.ts b/packages/forms/src/directives/reactive_directives/form_control_directive.ts index 03d13b926be99..c9ff4b167d1f7 100644 --- a/packages/forms/src/directives/reactive_directives/form_control_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_control_directive.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {Directive, EventEmitter, forwardRef, Inject, InjectionToken, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges} from '@angular/core'; +import {Directive, EventEmitter, forwardRef, Inject, inject, InjectionToken, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges} from '@angular/core'; import {FormControl} from '../../model/form_control'; import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor'; import {NgControl} from '../ng_control'; import {disabledAttrWarning} from '../reactive_errors'; -import {_ngModelWarning, cleanUpControl, isPropertyUpdated, selectValueAccessor, setUpControl} from '../shared'; +import {_ngModelWarning, CALL_SET_DISABLED_STATE, cleanUpControl, isPropertyUpdated, selectValueAccessor, setDisabledStateDefault, SetDisabledStateOption, setUpControl} from '../shared'; import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../validators'; @@ -108,7 +108,9 @@ export class FormControlDirective extends NgControl implements OnChanges, OnDest (AsyncValidator|AsyncValidatorFn)[], @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[], @Optional() @Inject(NG_MODEL_WITH_FORM_CONTROL_WARNING) private _ngModelWarningConfig: string| - null) { + null, + @Optional() @Inject(CALL_SET_DISABLED_STATE) private callSetDisabledState?: + SetDisabledStateOption|null) { super(); this._setValidators(validators); this._setAsyncValidators(asyncValidators); @@ -122,7 +124,7 @@ export class FormControlDirective extends NgControl implements OnChanges, OnDest if (previousForm) { cleanUpControl(previousForm, this, /* validateControlPresenceOnChange */ false); } - setUpControl(this.form, this); + setUpControl(this.form, this, this.callSetDisabledState); this.form.updateValueAndValidity({emitEvent: false}); } if (isPropertyUpdated(changes, this.viewModel)) { diff --git a/packages/forms/src/directives/reactive_directives/form_group_directive.ts b/packages/forms/src/directives/reactive_directives/form_group_directive.ts index 171f5c407c0ce..ee41c9fa8709b 100644 --- a/packages/forms/src/directives/reactive_directives/form_group_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_group_directive.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Directive, EventEmitter, forwardRef, Inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges} from '@angular/core'; +import {Directive, EventEmitter, forwardRef, Inject, inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges} from '@angular/core'; import {FormArray} from '../../model/form_array'; import {FormControl, isFormControl} from '../../model/form_control'; @@ -15,7 +15,7 @@ import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators'; import {ControlContainer} from '../control_container'; import {Form} from '../form_interface'; import {missingFormException} from '../reactive_errors'; -import {cleanUpControl, cleanUpFormContainer, cleanUpValidators, removeListItem, setUpControl, setUpFormContainer, setUpValidators, syncPendingControls} from '../shared'; +import {CALL_SET_DISABLED_STATE, cleanUpControl, cleanUpFormContainer, cleanUpValidators, removeListItem, SetDisabledStateOption, setUpControl, setUpFormContainer, setUpValidators, syncPendingControls} from '../shared'; import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../validators'; import {FormControlName} from './form_control_name'; @@ -96,7 +96,9 @@ export class FormGroupDirective extends ControlContainer implements Form, OnChan constructor( @Optional() @Self() @Inject(NG_VALIDATORS) validators: (Validator|ValidatorFn)[], @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: - (AsyncValidator|AsyncValidatorFn)[]) { + (AsyncValidator|AsyncValidatorFn)[], + @Optional() @Inject(CALL_SET_DISABLED_STATE) private callSetDisabledState?: + SetDisabledStateOption|null) { super(); this._setValidators(validators); this._setAsyncValidators(asyncValidators); @@ -164,7 +166,7 @@ export class FormGroupDirective extends ControlContainer implements Form, OnChan */ addControl(dir: FormControlName): FormControl { const ctrl: any = this.form.get(dir.path); - setUpControl(ctrl, dir); + setUpControl(ctrl, dir, this.callSetDisabledState); ctrl.updateValueAndValidity({emitEvent: false}); this.directives.push(dir); return ctrl; @@ -312,7 +314,7 @@ export class FormGroupDirective extends ControlContainer implements Form, OnChan // taken care of in the `removeControl` method invoked when corresponding `formControlName` // directive instance is being removed (invoked from `FormControlName.ngOnDestroy`). if (isFormControl(newCtrl)) { - setUpControl(newCtrl, dir); + setUpControl(newCtrl, dir, this.callSetDisabledState); (dir as {control: FormControl}).control = newCtrl; } } diff --git a/packages/forms/src/directives/shared.ts b/packages/forms/src/directives/shared.ts index 30dee87a1d991..25d5e292cc856 100644 --- a/packages/forms/src/directives/shared.ts +++ b/packages/forms/src/directives/shared.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ɵRuntimeError as RuntimeError} from '@angular/core'; +import {Inject, InjectionToken, ɵRuntimeError as RuntimeError} from '@angular/core'; import {RuntimeErrorCode} from '../errors'; import {AbstractControl} from '../model/abstract_model'; @@ -25,6 +25,22 @@ import {FormArrayName} from './reactive_directives/form_group_name'; import {ngModelWarning} from './reactive_errors'; import {AsyncValidatorFn, Validator, ValidatorFn} from './validators'; +/** + * Token to provide to allow SetDisabledState to always be called when a CVA is added, regardless of + * whether the control is disabled or enabled. + */ +export const CALL_SET_DISABLED_STATE = new InjectionToken('CallSetDisabledState'); + +/** + * The type for CALL_SET_DISABLED_STATE. + */ +export type SetDisabledStateOption = 'whenDisabledForLegacyCode'|'always'; + +/** + * Whether to use the fixed setDisabledState behavior by default. + */ +export const setDisabledStateDefault = 'always'; + export function controlPath(name: string|null, parent: ControlContainer): string[] { return [...parent.path!, name!]; } @@ -36,7 +52,10 @@ export function controlPath(name: string|null, parent: ControlContainer): string * @param control Form control instance that should be linked. * @param dir Directive that should be linked with a given control. */ -export function setUpControl(control: FormControl, dir: NgControl): void { +export function setUpControl( + control: FormControl, dir: NgControl, + callSetDisabledState: SetDisabledStateOption|null = setDisabledStateDefault): void { + if (callSetDisabledState == null) callSetDisabledState = setDisabledStateDefault; if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!control) _throwError(dir, 'Cannot find control with'); if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with'); @@ -46,8 +65,8 @@ export function setUpControl(control: FormControl, dir: NgControl): void { dir.valueAccessor!.writeValue(control.value); - if (control.disabled) { - dir.valueAccessor!.setDisabledState?.(true); + if (control.disabled || callSetDisabledState === 'always') { + dir.valueAccessor!.setDisabledState?.(control.disabled); } setUpViewChangePipeline(control, dir); diff --git a/packages/forms/src/form_providers.ts b/packages/forms/src/form_providers.ts index df86eb45bb76b..7f6d9e4ba6f88 100644 --- a/packages/forms/src/form_providers.ts +++ b/packages/forms/src/form_providers.ts @@ -9,6 +9,7 @@ import {ModuleWithProviders, NgModule} from '@angular/core'; import {InternalFormsSharedModule, NG_MODEL_WITH_FORM_CONTROL_WARNING, REACTIVE_DRIVEN_DIRECTIVES, TEMPLATE_DRIVEN_DIRECTIVES} from './directives'; +import {CALL_SET_DISABLED_STATE, setDisabledStateDefault, SetDisabledStateOption} from './directives/shared'; /** * Exports the required providers and directives for template-driven forms, @@ -27,6 +28,25 @@ import {InternalFormsSharedModule, NG_MODEL_WITH_FORM_CONTROL_WARNING, REACTIVE_ exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES] }) export class FormsModule { + /** + * @description + * Provides options for configuring the forms module. + * + * @param opts An object of configuration options + * * `callSetDisabledState` Configures whether to `always` call `setDisabledState`, which is more + * correct, or to only call it `whenDisabled`, which is the legacy behavior. + */ + static withConfig(opts: { + callSetDisabledState?: SetDisabledStateOption, + }): ModuleWithProviders { + return { + ngModule: FormsModule, + providers: [{ + provide: CALL_SET_DISABLED_STATE, + useValue: opts.callSetDisabledState ?? setDisabledStateDefault + }] + }; + } } /** @@ -54,14 +74,25 @@ export class ReactiveFormsModule { * @param opts An object of configuration options * * `warnOnNgModelWithFormControl` Configures when to emit a warning when an `ngModel` * binding is used with reactive form directives. + * * `callSetDisabledState` Configures whether to `always` call `setDisabledState`, which is more + * correct, or to only call it `whenDisabled`, which is the legacy behavior. */ static withConfig(opts: { - /** @deprecated as of v6 */ warnOnNgModelWithFormControl: 'never'|'once'|'always' - }): ModuleWithProviders { + /** @deprecated as of v6 */ warnOnNgModelWithFormControl?: 'never'|'once'| + 'always', + callSetDisabledState?: SetDisabledStateOption, + }): ModuleWithProviders { return { ngModule: ReactiveFormsModule, providers: [ - {provide: NG_MODEL_WITH_FORM_CONTROL_WARNING, useValue: opts.warnOnNgModelWithFormControl} + { + provide: NG_MODEL_WITH_FORM_CONTROL_WARNING, + useValue: opts.warnOnNgModelWithFormControl ?? 'always' + }, + { + provide: CALL_SET_DISABLED_STATE, + useValue: opts.callSetDisabledState ?? setDisabledStateDefault + } ] }; } diff --git a/packages/forms/test/value_accessor_integration_spec.ts b/packages/forms/test/value_accessor_integration_spec.ts index a3acdae37d825..0d231db63bfe1 100644 --- a/packages/forms/test/value_accessor_integration_spec.ts +++ b/packages/forms/test/value_accessor_integration_spec.ts @@ -1066,6 +1066,40 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util' expect(fixture.componentInstance.control.status).toEqual('DISABLED'); }); + describe('should support custom accessors with setDisabledState - formControlName', () => { + let fixture: ComponentFixture; + + beforeEach(() => { + fixture = initTest(CvaWithDisabledStateForm, CvaWithDisabledState); + }); + + it('sets the disabled state when the control is initally disabled', () => { + fixture.componentInstance.form = new FormGroup({ + 'login': new FormControl({value: 'aa', disabled: true}), + }); + fixture.detectChanges(); + + expect(fixture.componentInstance.form.status).toEqual('DISABLED'); + expect(fixture.componentInstance.form.get('login')!.status).toEqual('DISABLED'); + expect(fixture.debugElement.query(By.directive(CvaWithDisabledState)) + .nativeElement.textContent) + .toEqual('DISABLED'); + }); + + it('sets the enabled state when the control is initally enabled', () => { + fixture.componentInstance.form = new FormGroup({ + 'login': new FormControl({value: 'aa', disabled: false}), + }); + fixture.detectChanges(); + + expect(fixture.componentInstance.form.status).toEqual('VALID'); + expect(fixture.componentInstance.form.get('login')!.status).toEqual('VALID'); + expect(fixture.debugElement.query(By.directive(CvaWithDisabledState)) + .nativeElement.textContent) + .toEqual('ENABLED'); + }); + }); + it('should populate control in ngOnInit when injecting NgControl', () => { const fixture = initTest(MyInputForm, MyInput); fixture.componentInstance.form = new FormGroup({'login': new FormControl('aa')}); @@ -1439,6 +1473,38 @@ class WrappedValue implements ControlValueAccessor { } } +@Component({ + selector: 'cva-with-disabled-state', + template: `{{disabled ? 'DISABLED' : 'ENABLED'}}`, + providers: [ + {provide: NG_VALUE_ACCESSOR, multi: true, useExisting: CvaWithDisabledState}, + ] +}) +class CvaWithDisabledState implements ControlValueAccessor { + disabled!: boolean; + onChange!: Function; + + writeValue(value: any) {} + + registerOnChange(fn: (value: any) => void) {} + registerOnTouched(fn: any) {} + + setDisabledState(disabled: boolean) { + this.disabled = disabled; + } +} + +@Component({ + selector: 'wrapped-value-form', + template: ` +
+ +
` +}) +class CvaWithDisabledStateForm { + form!: FormGroup; +} + @Component({selector: 'my-input', template: ''}) export class MyInput implements ControlValueAccessor { @Output('input') onInput = new EventEmitter();