diff --git a/packages/forms/src/directives.ts b/packages/forms/src/directives.ts
index 41a298e90e..d2fda20449 100644
--- a/packages/forms/src/directives.ts
+++ b/packages/forms/src/directives.ts
@@ -37,7 +37,7 @@ export {NgModelGroup} from './directives/ng_model_group';
export {NumberValueAccessor} from './directives/number_value_accessor';
export {RadioControlValueAccessor} from './directives/radio_control_value_accessor';
export {RangeValueAccessor} from './directives/range_value_accessor';
-export {FormControlDirective} from './directives/reactive_directives/form_control_directive';
+export {FormControlDirective, NG_MODEL_WITH_FORM_CONTROL_WARNING} from './directives/reactive_directives/form_control_directive';
export {FormControlName} from './directives/reactive_directives/form_control_name';
export {FormGroupDirective} from './directives/reactive_directives/form_group_directive';
export {FormArrayName, FormGroupName} from './directives/reactive_directives/form_group_name';
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 6fd76607d2..8f36a785ad 100644
--- a/packages/forms/src/directives/reactive_directives/form_control_directive.ts
+++ b/packages/forms/src/directives/reactive_directives/form_control_directive.ts
@@ -6,16 +6,23 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Directive, EventEmitter, Inject, Input, OnChanges, Optional, Output, Self, SimpleChanges, forwardRef} from '@angular/core';
+import {Directive, EventEmitter, Inject, InjectionToken, Input, OnChanges, Optional, Output, Self, SimpleChanges, forwardRef} from '@angular/core';
import {FormControl} from '../../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor';
import {NgControl} from '../ng_control';
import {ReactiveErrors} from '../reactive_errors';
-import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from '../shared';
+import {_ngModelWarning, composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from '../shared';
import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../validators';
+
+/**
+ * Token to provide to turn off the ngModel warning on formControl and formControlName.
+ */
+export const NG_MODEL_WITH_FORM_CONTROL_WARNING =
+ new InjectionToken('NgModelWithFormControlWarning');
+
export const formControlBinding: any = {
provide: NgControl,
useExisting: forwardRef(() => FormControlDirective)
@@ -61,6 +68,72 @@ export const formControlBinding: any = {
*
* * **NgModule**: `ReactiveFormsModule`
*
+ * ### Use with ngModel
+ *
+ * Support for using the `ngModel` input property and `ngModelChange` event with reactive
+ * form directives has been deprecated in Angular v6 and will be removed in Angular v7.
+ *
+ * Now deprecated:
+ * ```html
+ *
+ * ```
+ *
+ * ```ts
+ * this.value = 'some value';
+ * ```
+ *
+ * This has been deprecated for a few reasons. First, developers have found this pattern
+ * confusing. It seems like the actual `ngModel` directive is being used, but in fact it's
+ * an input/output property named `ngModel` on the reactive form directive that simply
+ * approximates (some of) its behavior. Specifically, it allows getting/setting the value
+ * and intercepting value events. However, some of `ngModel`'s other features - like
+ * delaying updates with`ngModelOptions` or exporting the directive - simply don't work,
+ * which has understandably caused some confusion.
+ *
+ * In addition, this pattern mixes template-driven and reactive forms strategies, which
+ * we generally don't recommend because it doesn't take advantage of the full benefits of
+ * either strategy. Setting the value in the template violates the template-agnostic
+ * principles behind reactive forms, whereas adding a `FormControl`/`FormGroup` layer in
+ * the class removes the convenience of defining forms in the template.
+ *
+ * To update your code before v7, you'll want to decide whether to stick with reactive form
+ * directives (and get/set values using reactive forms patterns) or switch over to
+ * template-driven directives.
+ *
+ * After (choice 1 - use reactive forms):
+ *
+ * ```html
+ *
+ * ```
+ *
+ * ```ts
+ * this.control.setValue('some value');
+ * ```
+ *
+ * After (choice 2 - use template-driven forms):
+ *
+ * ```html
+ *
+ * ```
+ *
+ * ```ts
+ * this.value = 'some value';
+ * ```
+ *
+ * By default, when you use this pattern, you will see a deprecation warning once in dev
+ * mode. You can choose to silence this warning by providing a config for
+ * `ReactiveFormsModule` at import time:
+ *
+ * ```ts
+ * imports: [
+ * ReactiveFormsModule.withConfig({warnOnNgModelWithFormControl: 'never'});
+ * ]
+ * ```
+ *
+ * Alternatively, you can choose to surface a separate warning for each instance of this
+ * pattern with a config value of `"always"`. This may help to track down where in the code
+ * the pattern is being used as the code is being updated.
+ *
* @stable
*/
@Directive({selector: '[formControl]', providers: [formControlBinding], exportAs: 'ngForm'})
@@ -69,16 +142,39 @@ export class FormControlDirective extends NgControl implements OnChanges {
viewModel: any;
@Input('formControl') form: FormControl;
- @Input('ngModel') model: any;
- @Output('ngModelChange') update = new EventEmitter();
@Input('disabled')
set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); }
+ // TODO(kara): remove next 4 properties once deprecation period is over
+
+ /** @deprecated as of v6 */
+ @Input('ngModel') model: any;
+
+ /** @deprecated as of v6 */
+ @Output('ngModelChange') update = new EventEmitter();
+
+ /**
+ * Static property used to track whether any ngModel warnings have been sent across
+ * all instances of FormControlDirective. Used to support warning config of "once".
+ *
+ * @internal
+ */
+ static _ngModelWarningSentOnce = false;
+
+ /**
+ * Instance property used to track whether an ngModel warning has been sent out for this
+ * particular FormControlDirective instance. Used to support warning config of "always".
+ *
+ * @internal
+ */
+ _ngModelWarningSent = false;
+
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array,
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array,
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
- valueAccessors: ControlValueAccessor[]) {
+ valueAccessors: ControlValueAccessor[],
+ @Optional() @Inject(NG_MODEL_WITH_FORM_CONTROL_WARNING) private _ngModelWarningConfig: string|null) {
super();
this._rawValidators = validators || [];
this._rawAsyncValidators = asyncValidators || [];
@@ -94,6 +190,8 @@ export class FormControlDirective extends NgControl implements OnChanges {
this.form.updateValueAndValidity({emitEvent: false});
}
if (isPropertyUpdated(changes, this.viewModel)) {
+ _ngModelWarning(
+ 'formControl', FormControlDirective, this, this._ngModelWarningConfig);
this.form.setValue(this.model);
this.viewModel = this.model;
}
diff --git a/packages/forms/src/directives/reactive_directives/form_control_name.ts b/packages/forms/src/directives/reactive_directives/form_control_name.ts
index a53f69b68d..04747e608f 100644
--- a/packages/forms/src/directives/reactive_directives/form_control_name.ts
+++ b/packages/forms/src/directives/reactive_directives/form_control_name.ts
@@ -15,9 +15,10 @@ import {ControlContainer} from '../control_container';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor';
import {NgControl} from '../ng_control';
import {ReactiveErrors} from '../reactive_errors';
-import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor} from '../shared';
+import {_ngModelWarning, composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor} from '../shared';
import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../validators';
+import {NG_MODEL_WITH_FORM_CONTROL_WARNING} from './form_control_directive';
import {FormGroupDirective} from './form_group_directive';
import {FormArrayName, FormGroupName} from './form_group_name';
@@ -75,6 +76,76 @@ export const controlNameBinding: any = {
*
* **NgModule**: {@link ReactiveFormsModule}
*
+ * ### Use with ngModel
+ *
+ * Support for using the `ngModel` input property and `ngModelChange` event with reactive
+ * form directives has been deprecated in Angular v6 and will be removed in Angular v7.
+ *
+ * Now deprecated:
+ * ```html
+ *
+ * ```
+ *
+ * ```ts
+ * this.value = 'some value';
+ * ```
+ *
+ * This has been deprecated for a few reasons. First, developers have found this pattern
+ * confusing. It seems like the actual `ngModel` directive is being used, but in fact it's
+ * an input/output property named `ngModel` on the reactive form directive that simply
+ * approximates (some of) its behavior. Specifically, it allows getting/setting the value
+ * and intercepting value events. However, some of `ngModel`'s other features - like
+ * delaying updates with`ngModelOptions` or exporting the directive - simply don't work,
+ * which has understandably caused some confusion.
+ *
+ * In addition, this pattern mixes template-driven and reactive forms strategies, which
+ * we generally don't recommend because it doesn't take advantage of the full benefits of
+ * either strategy. Setting the value in the template violates the template-agnostic
+ * principles behind reactive forms, whereas adding a `FormControl`/`FormGroup` layer in
+ * the class removes the convenience of defining forms in the template.
+ *
+ * To update your code before v7, you'll want to decide whether to stick with reactive form
+ * directives (and get/set values using reactive forms patterns) or switch over to
+ * template-driven directives.
+ *
+ * After (choice 1 - use reactive forms):
+ *
+ * ```html
+ *
+ * ```
+ *
+ * ```ts
+ * this.form.get('first').setValue('some value');
+ * ```
+ *
+ * After (choice 2 - use template-driven forms):
+ *
+ * ```html
+ *
+ * ```
+ *
+ * ```ts
+ * this.value = 'some value';
+ * ```
+ *
+ * By default, when you use this pattern, you will see a deprecation warning once in dev
+ * mode. You can choose to silence this warning by providing a config for
+ * `ReactiveFormsModule` at import time:
+ *
+ * ```ts
+ * imports: [
+ * ReactiveFormsModule.withConfig({warnOnNgModelWithFormControl: 'never'});
+ * ]
+ * ```
+ *
+ * Alternatively, you can choose to surface a separate warning for each instance of this
+ * pattern with a config value of `"always"`. This may help to track down where in the code
+ * the pattern is being used as the code is being updated.
+ *
* @stable
*/
@Directive({selector: '[formControlName]', providers: [controlNameBinding]})
@@ -86,18 +157,41 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
@Input('formControlName') name: string;
- // TODO(kara): Replace ngModel with reactive API
- @Input('ngModel') model: any;
- @Output('ngModelChange') update = new EventEmitter();
@Input('disabled')
set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); }
+ // TODO(kara): remove next 4 properties once deprecation period is over
+
+ /** @deprecated as of v6 */
+ @Input('ngModel') model: any;
+
+ /** @deprecated as of v6 */
+ @Output('ngModelChange') update = new EventEmitter();
+
+ /**
+ * Static property used to track whether any ngModel warnings have been sent across
+ * all instances of FormControlName. Used to support warning config of "once".
+ *
+ * @internal
+ */
+ static _ngModelWarningSentOnce = false;
+
+ /**
+ * Instance property used to track whether an ngModel warning has been sent out for this
+ * particular FormControlName instance. Used to support warning config of "always".
+ *
+ * @internal
+ */
+ _ngModelWarningSent = false;
+
constructor(
@Optional() @Host() @SkipSelf() parent: ControlContainer,
@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array,
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators:
Array,
- @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
+ @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[],
+ @Optional() @Inject(NG_MODEL_WITH_FORM_CONTROL_WARNING) private _ngModelWarningConfig: string|
+ null) {
super();
this._parent = parent;
this._rawValidators = validators || [];
@@ -108,6 +202,7 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
ngOnChanges(changes: SimpleChanges) {
if (!this._added) this._setUpControl();
if (isPropertyUpdated(changes, this.viewModel)) {
+ _ngModelWarning('formControlName', FormControlName, this, this._ngModelWarningConfig);
this.viewModel = this.model;
this.formDirective.updateModel(this, this.model);
}
diff --git a/packages/forms/src/directives/reactive_errors.ts b/packages/forms/src/directives/reactive_errors.ts
index 0f9286fb8e..acca6a319f 100644
--- a/packages/forms/src/directives/reactive_errors.ts
+++ b/packages/forms/src/directives/reactive_errors.ts
@@ -74,4 +74,17 @@ export class ReactiveErrors {
});
`);
}
+
+ static ngModelWarning(directiveName: string): void {
+ console.warn(`
+ It looks like you're using ngModel on the same form field as ${directiveName}.
+ Support for using the ngModel input property and ngModelChange event with
+ reactive form directives has been deprecated in Angular v6 and will be removed
+ in Angular v7.
+
+ For more information on this, see our API docs here:
+ https://angular.io/api/forms/${directiveName === 'formControl' ? 'FormControlDirective'
+ : 'FormControlName'}#use-with-ngmodel
+ `);
+ }
}
diff --git a/packages/forms/src/directives/shared.ts b/packages/forms/src/directives/shared.ts
index 0d853f84fc..38a3779b7f 100644
--- a/packages/forms/src/directives/shared.ts
+++ b/packages/forms/src/directives/shared.ts
@@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ɵlooseIdentical as looseIdentical} from '@angular/core';
+import {isDevMode, ɵlooseIdentical as looseIdentical} from '@angular/core';
+
import {FormArray, FormControl, FormGroup} from '../model';
import {Validators} from '../validators';
import {AbstractControlDirective} from './abstract_control_directive';
@@ -21,6 +22,7 @@ import {NumberValueAccessor} from './number_value_accessor';
import {RadioControlValueAccessor} from './radio_control_value_accessor';
import {RangeValueAccessor} from './range_value_accessor';
import {FormArrayName} from './reactive_directives/form_group_name';
+import {ReactiveErrors} from './reactive_errors';
import {SelectControlValueAccessor} from './select_control_value_accessor';
import {SelectMultipleControlValueAccessor} from './select_multiple_control_value_accessor';
import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators';
@@ -216,3 +218,17 @@ export function removeDir(list: T[], el: T): void {
const index = list.indexOf(el);
if (index > -1) list.splice(index, 1);
}
+
+// TODO(kara): remove after deprecation period
+export function _ngModelWarning(
+ name: string, type: {_ngModelWarningSentOnce: boolean},
+ instance: {_ngModelWarningSent: boolean}, warningConfig: string | null) {
+ if (!isDevMode() || warningConfig === 'never') return;
+
+ if (((warningConfig === null || warningConfig === 'once') && !type._ngModelWarningSentOnce) ||
+ (warningConfig === 'always' && !instance._ngModelWarningSent)) {
+ ReactiveErrors.ngModelWarning(name);
+ type._ngModelWarningSentOnce = true;
+ instance._ngModelWarningSent = true;
+ }
+}
diff --git a/packages/forms/src/form_providers.ts b/packages/forms/src/form_providers.ts
index a7dd83bdf8..fd2b452a89 100644
--- a/packages/forms/src/form_providers.ts
+++ b/packages/forms/src/form_providers.ts
@@ -6,13 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {NgModule} from '@angular/core';
+import {ModuleWithProviders, NgModule} from '@angular/core';
-import {InternalFormsSharedModule, REACTIVE_DRIVEN_DIRECTIVES, TEMPLATE_DRIVEN_DIRECTIVES} from './directives';
+import {InternalFormsSharedModule, NG_MODEL_WITH_FORM_CONTROL_WARNING, REACTIVE_DRIVEN_DIRECTIVES, TEMPLATE_DRIVEN_DIRECTIVES} from './directives';
import {RadioControlRegistry} from './directives/radio_control_value_accessor';
import {FormBuilder} from './form_builder';
+
/**
* The ng module for forms.
* @stable
@@ -35,4 +36,15 @@ export class FormsModule {
exports: [InternalFormsSharedModule, REACTIVE_DRIVEN_DIRECTIVES]
})
export class ReactiveFormsModule {
+ static withConfig(opts: {
+ /** @deprecated as of v6 */ warnOnNgModelWithFormControl: 'never' | 'once' | 'always'
+ }): ModuleWithProviders {
+ return {
+ ngModule: ReactiveFormsModule,
+ providers: [{
+ provide: NG_MODEL_WITH_FORM_CONTROL_WARNING,
+ useValue: opts.warnOnNgModelWithFormControl
+ }]
+ };
+ }
}
diff --git a/packages/forms/test/directives_spec.ts b/packages/forms/test/directives_spec.ts
index dd81a8b4f0..ef1fd2711e 100644
--- a/packages/forms/test/directives_spec.ts
+++ b/packages/forms/test/directives_spec.ts
@@ -137,7 +137,7 @@ function asyncValidator(expected: any, timeout = 0) {
form.form = formModel;
loginControlDir = new FormControlName(
- form, [Validators.required], [asyncValidator('expected')], [defaultAccessor]);
+ form, [Validators.required], [asyncValidator('expected')], [defaultAccessor], null);
loginControlDir.name = 'login';
loginControlDir.valueAccessor = new DummyControlValueAccessor();
});
@@ -168,7 +168,7 @@ function asyncValidator(expected: any, timeout = 0) {
describe('addControl', () => {
it('should throw when no control found', () => {
- const dir = new FormControlName(form, null !, null !, [defaultAccessor]);
+ const dir = new FormControlName(form, null !, null !, [defaultAccessor], null);
dir.name = 'invalidName';
expect(() => form.addControl(dir))
@@ -176,7 +176,7 @@ function asyncValidator(expected: any, timeout = 0) {
});
it('should throw for a named control when no value accessor', () => {
- const dir = new FormControlName(form, null !, null !, null !);
+ const dir = new FormControlName(form, null !, null !, null !, null);
dir.name = 'login';
expect(() => form.addControl(dir))
@@ -185,7 +185,7 @@ function asyncValidator(expected: any, timeout = 0) {
it('should throw when no value accessor with path', () => {
const group = new FormGroupName(form, null !, null !);
- const dir = new FormControlName(group, null !, null !, null !);
+ const dir = new FormControlName(group, null !, null !, null !, null);
group.name = 'passwords';
dir.name = 'password';
@@ -496,7 +496,7 @@ function asyncValidator(expected: any, timeout = 0) {
};
beforeEach(() => {
- controlDir = new FormControlDirective([Validators.required], [], [defaultAccessor]);
+ controlDir = new FormControlDirective([Validators.required], [], [defaultAccessor], null);
controlDir.valueAccessor = new DummyControlValueAccessor();
control = new FormControl(null);
@@ -648,7 +648,7 @@ function asyncValidator(expected: any, timeout = 0) {
const parent = new FormGroupDirective([], []);
parent.form = new FormGroup({'name': formModel});
- controlNameDir = new FormControlName(parent, [], [], [defaultAccessor]);
+ controlNameDir = new FormControlName(parent, [], [], [defaultAccessor], null);
controlNameDir.name = 'name';
(controlNameDir as{control: FormControl}).control = formModel;
});
diff --git a/packages/forms/test/reactive_integration_spec.ts b/packages/forms/test/reactive_integration_spec.ts
index 9459bfb822..81b4edef29 100644
--- a/packages/forms/test/reactive_integration_spec.ts
+++ b/packages/forms/test/reactive_integration_spec.ts
@@ -1627,9 +1627,94 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
describe('ngModel interactions', () => {
+ describe('deprecation warnings', () => {
+ let warnSpy: any;
+
+ beforeEach(() => {
+ warnSpy = jasmine.createSpy('warn');
+ console.warn = warnSpy;
+ });
+
+ it('should warn once by default when using ngModel with formControlName', fakeAsync(() => {
+ const fixture = initTest(FormGroupNgModel);
+ fixture.componentInstance.form =
+ new FormGroup({'login': new FormControl(''), 'password': new FormControl('')});
+ fixture.detectChanges();
+ tick();
+
+ expect(warnSpy.calls.count()).toEqual(1);
+ expect(warnSpy.calls.mostRecent().args[0])
+ .toMatch(
+ /It looks like you're using ngModel on the same form field as formControlName/gi);
+
+ fixture.componentInstance.login = 'some value';
+ fixture.detectChanges();
+ tick();
+
+ expect(warnSpy.calls.count()).toEqual(1);
+ }));
+
+ it('should warn once by default when using ngModel with formControl', fakeAsync(() => {
+ const fixture = initTest(FormControlNgModel);
+ fixture.componentInstance.control = new FormControl('');
+ fixture.componentInstance.passwordControl = new FormControl('');
+ fixture.detectChanges();
+ tick();
+
+ expect(warnSpy.calls.count()).toEqual(1);
+ expect(warnSpy.calls.mostRecent().args[0])
+ .toMatch(
+ /It looks like you're using ngModel on the same form field as formControl/gi);
+
+ fixture.componentInstance.login = 'some value';
+ fixture.detectChanges();
+ tick();
+
+ expect(warnSpy.calls.count()).toEqual(1);
+ }));
+
+ it('should warn once for each instance when global provider is provided with "always"',
+ fakeAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [FormControlNgModel],
+ imports:
+ [ReactiveFormsModule.withConfig({warnOnNgModelWithFormControl: 'always'})]
+ });
+
+ const fixture = TestBed.createComponent(FormControlNgModel);
+ fixture.componentInstance.control = new FormControl('');
+ fixture.componentInstance.passwordControl = new FormControl('');
+ fixture.detectChanges();
+ tick();
+
+ expect(warnSpy.calls.count()).toEqual(2);
+ expect(warnSpy.calls.mostRecent().args[0])
+ .toMatch(
+ /It looks like you're using ngModel on the same form field as formControl/gi);
+ }));
+
+ it('should silence warnings when global provider is provided with "never"',
+ fakeAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [FormControlNgModel],
+ imports: [ReactiveFormsModule.withConfig({warnOnNgModelWithFormControl: 'never'})]
+ });
+
+ const fixture = TestBed.createComponent(FormControlNgModel);
+ fixture.componentInstance.control = new FormControl('');
+ fixture.componentInstance.passwordControl = new FormControl('');
+ fixture.detectChanges();
+ tick();
+
+ expect(warnSpy).not.toHaveBeenCalled();
+ }));
+
+ });
+
it('should support ngModel for complex forms', fakeAsync(() => {
const fixture = initTest(FormGroupNgModel);
- fixture.componentInstance.form = new FormGroup({'login': new FormControl('')});
+ fixture.componentInstance.form =
+ new FormGroup({'login': new FormControl(''), 'password': new FormControl('')});
fixture.componentInstance.login = 'oldValue';
fixture.detectChanges();
tick();
@@ -1647,6 +1732,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
it('should support ngModel for single fields', fakeAsync(() => {
const fixture = initTest(FormControlNgModel);
fixture.componentInstance.control = new FormControl('');
+ fixture.componentInstance.passwordControl = new FormControl('');
fixture.componentInstance.login = 'oldValue';
fixture.detectChanges();
tick();
@@ -1665,6 +1751,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
if (isNode) return;
const fixture = initTest(FormControlNgModel);
fixture.componentInstance.control = new FormControl('');
+ fixture.componentInstance.passwordControl = new FormControl('');
fixture.detectChanges();
tick();
@@ -1682,7 +1769,8 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
it('should work with updateOn submit', fakeAsync(() => {
const fixture = initTest(FormGroupNgModel);
- const formGroup = new FormGroup({login: new FormControl('', {updateOn: 'submit'})});
+ const formGroup = new FormGroup(
+ {login: new FormControl('', {updateOn: 'submit'}), password: new FormControl('')});
fixture.componentInstance.form = formGroup;
fixture.componentInstance.login = 'initial';
fixture.detectChanges();
@@ -2449,20 +2537,27 @@ class FormArrayNestedGroup {
template: `
`
})
class FormGroupNgModel {
form: FormGroup;
login: string;
+ password: string;
}
@Component({
selector: 'form-control-ng-model',
- template: ``
+ template: `
+
+
+ `
})
class FormControlNgModel {
control: FormControl;
login: string;
+ passwordControl: FormControl;
+ password: string;
}
@Component({
diff --git a/tools/public_api_guard/forms/forms.d.ts b/tools/public_api_guard/forms/forms.d.ts
index a718a14a68..f9b72df3cc 100644
--- a/tools/public_api_guard/forms/forms.d.ts
+++ b/tools/public_api_guard/forms/forms.d.ts
@@ -256,12 +256,12 @@ export declare class FormControlDirective extends NgControl implements OnChanges
readonly control: FormControl;
form: FormControl;
isDisabled: boolean;
- model: any;
+ /** @deprecated */ model: any;
readonly path: string[];
- update: EventEmitter<{}>;
+ /** @deprecated */ update: EventEmitter<{}>;
readonly validator: ValidatorFn | null;
viewModel: any;
- constructor(validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]);
+ constructor(validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[], _ngModelWarningConfig: string | null);
ngOnChanges(changes: SimpleChanges): void;
viewToModelUpdate(newValue: any): void;
}
@@ -272,12 +272,12 @@ export declare class FormControlName extends NgControl implements OnChanges, OnD
readonly control: FormControl;
readonly formDirective: any;
isDisabled: boolean;
- model: any;
+ /** @deprecated */ model: any;
name: string;
readonly path: string[];
- update: EventEmitter<{}>;
+ /** @deprecated */ update: EventEmitter<{}>;
readonly validator: ValidatorFn | null;
- constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]);
+ constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[], _ngModelWarningConfig: string | null);
ngOnChanges(changes: SimpleChanges): void;
ngOnDestroy(): void;
viewToModelUpdate(newValue: any): void;
@@ -491,6 +491,8 @@ export declare class RadioControlValueAccessor implements ControlValueAccessor,
/** @stable */
export declare class ReactiveFormsModule {
+ static withConfig(opts: { warnOnNgModelWithFormControl: 'never' | 'once' | 'always';
+ }): ModuleWithProviders;
}
/** @stable */