refactor(forms): `ngForm` element selector has been deprecated in favor of `ng-form` (#23721)
This has been deprecated to keep selector consistent with other core Angular selectors. As element selectors are in kebab-case. Now deprecated: ``` <ngForm #myForm="ngForm"> ``` After: ``` <ng-form #myForm="ngForm"> ``` You can also choose to supress this warnings by providing a config for `FormsModule` during import: ```ts imports: [ FormsModule.withConfig({warnOnDeprecatedNgFormSelector: 'never'}); ] Closes: #23678 PR Close #23721
This commit is contained in:
parent
5982425436
commit
3ba5220839
|
@ -12,6 +12,7 @@ import {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor
|
|||
import {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
import {NgControlStatus, NgControlStatusGroup} from './directives/ng_control_status';
|
||||
import {NgForm} from './directives/ng_form';
|
||||
import {NgFormSelectorWarning} from './directives/ng_form_selector_warning';
|
||||
import {NgModel} from './directives/ng_model';
|
||||
import {NgModelGroup} from './directives/ng_model_group';
|
||||
import {NgNoValidate} from './directives/ng_no_validate_directive';
|
||||
|
@ -32,6 +33,7 @@ export {DefaultValueAccessor} from './directives/default_value_accessor';
|
|||
export {NgControl} from './directives/ng_control';
|
||||
export {NgControlStatus, NgControlStatusGroup} from './directives/ng_control_status';
|
||||
export {NgForm} from './directives/ng_form';
|
||||
export {NG_FORM_SELECTOR_WARNING, NgFormSelectorWarning} from './directives/ng_form_selector_warning';
|
||||
export {NgModel} from './directives/ng_model';
|
||||
export {NgModelGroup} from './directives/ng_model_group';
|
||||
export {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
|
@ -65,7 +67,8 @@ export const SHARED_FORM_DIRECTIVES: Type<any>[] = [
|
|||
EmailValidator,
|
||||
];
|
||||
|
||||
export const TEMPLATE_DRIVEN_DIRECTIVES: Type<any>[] = [NgModel, NgModelGroup, NgForm];
|
||||
export const TEMPLATE_DRIVEN_DIRECTIVES: Type<any>[] =
|
||||
[NgModel, NgModelGroup, NgForm, NgFormSelectorWarning];
|
||||
|
||||
export const REACTIVE_DRIVEN_DIRECTIVES: Type<any>[] =
|
||||
[FormControlDirective, FormGroupDirective, FormControlName, FormGroupName, FormArrayName];
|
||||
|
|
|
@ -55,16 +55,31 @@ const resolvedPromise = Promise.resolve(null);
|
|||
* unnecessary because the `<form>` tags are inert. In that case, you would
|
||||
* refrain from using the `formGroup` directive.
|
||||
*
|
||||
* Support for using `ngForm` element selector has been deprecated in Angular v6 and will be removed
|
||||
* in Angular v9.
|
||||
*
|
||||
* This has been deprecated to keep selectors consistent with other core Angular selectors,
|
||||
* as element selectors are typically written in kebab-case.
|
||||
*
|
||||
* Now deprecated:
|
||||
* ```html
|
||||
* <ngForm #myForm="ngForm">
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```html
|
||||
* <ng-form #myForm="ngForm">
|
||||
* ```
|
||||
*
|
||||
* {@example forms/ts/simpleForm/simple_form_example.ts region='Component'}
|
||||
*
|
||||
* * **npm package**: `@angular/forms`
|
||||
*
|
||||
* * **NgModule**: `FormsModule`
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'form:not([ngNoForm]):not([formGroup]),ngForm,[ngForm]',
|
||||
selector: 'form:not([ngNoForm]):not([formGroup]),ngForm,ng-form,[ngForm]',
|
||||
providers: [formDirectiveProvider],
|
||||
host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'},
|
||||
outputs: ['ngSubmit'],
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Inject, InjectionToken, Optional} from '@angular/core';
|
||||
import {TemplateDrivenErrors} from './template_driven_errors';
|
||||
|
||||
/**
|
||||
* Token to provide to turn off the warning when using 'ngForm' deprecated selector.
|
||||
*/
|
||||
export const NG_FORM_SELECTOR_WARNING = new InjectionToken('NgFormSelectorWarning');
|
||||
|
||||
/**
|
||||
* This directive is solely used to display warnings when the deprecated `ngForm` selector is used.
|
||||
*
|
||||
* @deprecated in Angular v6 and will be removed in Angular v9.
|
||||
*
|
||||
*/
|
||||
@Directive({selector: 'ngForm'})
|
||||
export class NgFormSelectorWarning {
|
||||
/**
|
||||
* Static property used to track whether the deprecation warning for this selector has been sent.
|
||||
* Used to support warning config of "once".
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static _ngFormWarning = false;
|
||||
|
||||
constructor(@Optional() @Inject(NG_FORM_SELECTOR_WARNING) ngFormWarning: string|null) {
|
||||
if (((!ngFormWarning || ngFormWarning === 'once') && !NgFormSelectorWarning._ngFormWarning) ||
|
||||
ngFormWarning === 'always') {
|
||||
TemplateDrivenErrors.ngFormWarning();
|
||||
NgFormSelectorWarning._ngFormWarning = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,4 +57,21 @@ export class TemplateDrivenErrors {
|
|||
|
||||
${Examples.ngModelGroup}`);
|
||||
}
|
||||
|
||||
static ngFormWarning() {
|
||||
console.warn(`
|
||||
It looks like you're using 'ngForm'.
|
||||
|
||||
Support for using the 'ngForm' element selector has been deprecated in Angular v6 and will be removed
|
||||
in Angular v9.
|
||||
|
||||
Use 'ng-form' instead.
|
||||
|
||||
Before:
|
||||
<ngForm #myForm="ngForm">
|
||||
|
||||
After:
|
||||
<ng-form #myForm="ngForm">
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,10 @@
|
|||
|
||||
import {ModuleWithProviders, NgModule} from '@angular/core';
|
||||
|
||||
import {InternalFormsSharedModule, NG_MODEL_WITH_FORM_CONTROL_WARNING, REACTIVE_DRIVEN_DIRECTIVES, TEMPLATE_DRIVEN_DIRECTIVES} from './directives';
|
||||
import {InternalFormsSharedModule, NG_FORM_SELECTOR_WARNING, 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.
|
||||
*
|
||||
|
@ -24,6 +22,15 @@ import {FormBuilder} from './form_builder';
|
|||
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
|
||||
})
|
||||
export class FormsModule {
|
||||
static withConfig(opts: {
|
||||
/** @deprecated as of v6 */ warnOnDeprecatedNgFormSelector?: 'never' | 'once' | 'always',
|
||||
}): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: FormsModule,
|
||||
providers:
|
||||
[{provide: NG_FORM_SELECTOR_WARNING, useValue: opts.warnOnDeprecatedNgFormSelector}]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,6 +28,7 @@ export {Form} from './directives/form_interface';
|
|||
export {NgControl} from './directives/ng_control';
|
||||
export {NgControlStatus, NgControlStatusGroup} from './directives/ng_control_status';
|
||||
export {NgForm} from './directives/ng_form';
|
||||
export {NgFormSelectorWarning} from './directives/ng_form_selector_warning';
|
||||
export {NgModel} from './directives/ng_model';
|
||||
export {NgModelGroup} from './directives/ng_model_group';
|
||||
export {RadioControlValueAccessor} from './directives/radio_control_value_accessor';
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Component, Directive, Type, forwardRef} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {AbstractControl, AsyncValidator, COMPOSITION_BUFFER_MODE, FormControl, FormsModule, NG_ASYNC_VALIDATORS, NgForm, NgModel} from '@angular/forms';
|
||||
import {AbstractControl, AsyncValidator, COMPOSITION_BUFFER_MODE, FormControl, FormsModule, NG_ASYNC_VALIDATORS, NgForm, NgFormSelectorWarning, NgModel} from '@angular/forms';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
|
||||
|
@ -1630,6 +1630,61 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
|||
}));
|
||||
});
|
||||
|
||||
describe('ngForm deprecation warnings', () => {
|
||||
let warnSpy: jasmine.Spy;
|
||||
|
||||
@Component({selector: 'ng-form-deprecated', template: `<ngForm></ngForm><ngForm></ngForm>`})
|
||||
class ngFormDeprecated {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
(NgFormSelectorWarning as any)._ngFormWarning = false;
|
||||
|
||||
warnSpy = spyOn(console, 'warn');
|
||||
});
|
||||
|
||||
describe(`when using the deprecated 'ngForm' selector`, () => {
|
||||
it(`should only warn once when global provider is provided with "once"`, () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ngFormDeprecated],
|
||||
imports: [FormsModule.withConfig({warnOnDeprecatedNgFormSelector: 'once'})]
|
||||
});
|
||||
TestBed.createComponent(ngFormDeprecated);
|
||||
expect(warnSpy).toHaveBeenCalledTimes(1);
|
||||
expect(warnSpy.calls.mostRecent().args[0])
|
||||
.toMatch(/It looks like you're using 'ngForm'/gi);
|
||||
});
|
||||
|
||||
it(`should only warn once by default`, () => {
|
||||
initTest(ngFormDeprecated);
|
||||
expect(warnSpy).toHaveBeenCalledTimes(1);
|
||||
expect(warnSpy.calls.mostRecent().args[0])
|
||||
.toMatch(/It looks like you're using 'ngForm'/gi);
|
||||
});
|
||||
|
||||
it(`should not warn when global provider is provided with "never"`, () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ngFormDeprecated],
|
||||
imports: [FormsModule.withConfig({warnOnDeprecatedNgFormSelector: 'never'})]
|
||||
});
|
||||
TestBed.createComponent(ngFormDeprecated);
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it(`should only warn for each instance when global provider is provided with "always"`,
|
||||
() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ngFormDeprecated],
|
||||
imports: [FormsModule.withConfig({warnOnDeprecatedNgFormSelector: 'always'})]
|
||||
});
|
||||
|
||||
TestBed.createComponent(ngFormDeprecated);
|
||||
expect(warnSpy).toHaveBeenCalledTimes(2);
|
||||
expect(warnSpy.calls.mostRecent().args[0])
|
||||
.toMatch(/It looks like you're using 'ngForm'/gi);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1845,7 +1900,7 @@ class NgModelAsyncValidation {
|
|||
selector: 'ng-model-changes-form',
|
||||
template: `
|
||||
<form>
|
||||
<input name="async" [ngModel]="name" (ngModelChange)="log()"
|
||||
<input name="async" [ngModel]="name" (ngModelChange)="log()"
|
||||
[ngModelOptions]="options">
|
||||
</form>
|
||||
`
|
||||
|
|
|
@ -330,6 +330,8 @@ export declare class FormGroupName extends AbstractFormGroupDirective implements
|
|||
}
|
||||
|
||||
export declare class FormsModule {
|
||||
static withConfig(opts: { warnOnDeprecatedNgFormSelector?: 'never' | 'once' | 'always';
|
||||
}): ModuleWithProviders;
|
||||
}
|
||||
|
||||
export declare class MaxLengthValidator implements Validator, OnChanges {
|
||||
|
@ -398,6 +400,11 @@ export declare class NgForm extends ControlContainer implements Form, AfterViewI
|
|||
updateModel(dir: NgControl, value: any): void;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
export declare class NgFormSelectorWarning {
|
||||
constructor(ngFormWarning: string | null);
|
||||
}
|
||||
|
||||
export declare class NgModel extends NgControl implements OnChanges, OnDestroy {
|
||||
readonly asyncValidator: AsyncValidatorFn | null;
|
||||
readonly control: FormControl;
|
||||
|
|
Loading…
Reference in New Issue