Skip to content

Commit

Permalink
fix(forms): properly cleanup in cases when FormControlName has no CVA (
Browse files Browse the repository at this point in the history
…#40526)

PR #39235 introduced additional cleanup logic for form controls and directives. The cleanup logic relies
on the presence of ControlValueAccessor instances on FormControlName and FormControl directives. In general
these fields are present and there are also checks to make sure that the mentioned directive instances are
created with CVAs. However some scenarios (primarily tests) may invoke the logic in a way that the directive
instance would not be fully initialized, thus causing CVA to be absent. As a result, the cleanup logic fails
while trying to call some methods on associated CVA instances.

This commit updates the cleanup logic to take into account the situation when CVA is not present.

Fixes #40521.

PR Close #40526
  • Loading branch information
AndrewKushnir authored and thePunderWoman committed Jan 25, 2021
1 parent 3c785e5 commit 72fc6aa
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 3 deletions.
11 changes: 9 additions & 2 deletions packages/forms/src/directives/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
28 changes: 27 additions & 1 deletion packages/forms/test/reactive_integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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: `
<form [formGroup]="form">
<div formControlName="control"></div>
</form>
`
})
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();
});
});
});
}
Expand Down

0 comments on commit 72fc6aa

Please sign in to comment.