diff --git a/packages/forms/src/directives/shared.ts b/packages/forms/src/directives/shared.ts index 6d4d6441ff..17c0a1d75a 100644 --- a/packages/forms/src/directives/shared.ts +++ b/packages/forms/src/directives/shared.ts @@ -75,8 +75,15 @@ export function cleanUpControl( } }; - dir.valueAccessor!.registerOnChange(noop); - dir.valueAccessor!.registerOnTouched(noop); + // The `valueAccessor` field is typically defined on FromControl and FormControlName directive + // instances and there is a logic in `selectValueAccessor` function that throws if it's not the + // case. We still check the presence of `valueAccessor` before invoking its methods to make sure + // that cleanup works correctly if app code or tests are setup to ignore the error thrown from + // `selectValueAccessor`. See https://github.com/angular/angular/issues/40521. + if (dir.valueAccessor) { + dir.valueAccessor.registerOnChange(noop); + dir.valueAccessor.registerOnTouched(noop); + } cleanUpValidators(control, dir, /* handleOnValidatorChange */ true); diff --git a/packages/forms/test/reactive_integration_spec.ts b/packages/forms/test/reactive_integration_spec.ts index 379ef229b9..fcdb1c6c45 100644 --- a/packages/forms/test/reactive_integration_spec.ts +++ b/packages/forms/test/reactive_integration_spec.ts @@ -7,7 +7,7 @@ */ import {ɵgetDOM as getDOM} from '@angular/common'; -import {Component, Directive, forwardRef, Input, OnDestroy, Type} from '@angular/core'; +import {Component, Directive, forwardRef, Input, NgModule, OnDestroy, Type} from '@angular/core'; import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; import {expect} from '@angular/core/testing/src/testing_internal'; import {AbstractControl, AsyncValidator, AsyncValidatorFn, COMPOSITION_BUFFER_MODE, ControlValueAccessor, DefaultValueAccessor, FormArray, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormsModule, NG_ASYNC_VALIDATORS, NG_VALIDATORS, NG_VALUE_ACCESSOR, ReactiveFormsModule, Validator, Validators} from '@angular/forms'; @@ -4127,6 +4127,32 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]'); valueChanges: {group: {control: 'Updated value'}}, }); }); + + // See https://github.com/angular/angular/issues/40521. + it('should properly clean up when FormControlName has no CVA', () => { + @Component({ + selector: 'no-cva-compo', + template: ` +
+ ` + }) + class NoCVAComponent { + form = new FormGroup({control: new FormControl()}); + } + + const fixture = initTest(NoCVAComponent); + expect(() => { + fixture.detectChanges(); + }).toThrowError('No value accessor for form control with name: \'control\''); + + // Making sure that cleanup between tests doesn't cause any issues + // for not fully initialized controls. + expect(() => { + fixture.destroy(); + }).not.toThrow(); + }); }); }); }