feat(forms): add ability to reset forms (#9974)

Closes #4914
Closes #4933
This commit is contained in:
Kara 2016-07-12 15:02:25 -07:00 committed by GitHub
parent 806a25413c
commit da8eb9f8b8
9 changed files with 705 additions and 23 deletions

View File

@ -48,4 +48,8 @@ export abstract class AbstractControlDirective {
} }
get path(): string[] { return null; } get path(): string[] { return null; }
reset(value: any = undefined): void {
if (isPresent(this.control)) this.control.reset(value);
}
} }

View File

@ -86,9 +86,7 @@ export const formDirectiveProvider: any =
@Directive({ @Directive({
selector: 'form:not([ngNoForm]):not([formGroup]),ngForm,[ngForm]', selector: 'form:not([ngNoForm]):not([formGroup]),ngForm,[ngForm]',
providers: [formDirectiveProvider], providers: [formDirectiveProvider],
host: { host: {'(submit)': 'onSubmit()', '(reset)': 'onReset()'},
'(submit)': 'onSubmit()',
},
outputs: ['ngSubmit'], outputs: ['ngSubmit'],
exportAs: 'ngForm' exportAs: 'ngForm'
}) })
@ -172,6 +170,8 @@ export class NgForm extends ControlContainer implements Form {
return false; return false;
} }
onReset(): void { this.form.reset(); }
/** @internal */ /** @internal */
_findContainer(path: string[]): FormGroup { _findContainer(path: string[]): FormGroup {
path.pop(); path.pop();

View File

@ -135,6 +135,7 @@ export class NgModel extends NgControl implements OnChanges,
} }
private _updateValue(value: any): void { private _updateValue(value: any): void {
PromiseWrapper.scheduleMicrotask(() => { this.control.updateValue(value); }); PromiseWrapper.scheduleMicrotask(
() => { this.control.updateValue(value, {emitViewToModelChange: false}); });
} }
} }

View File

@ -105,7 +105,7 @@ export const formDirectiveProvider: any =
@Directive({ @Directive({
selector: '[formGroup]', selector: '[formGroup]',
providers: [formDirectiveProvider], providers: [formDirectiveProvider],
host: {'(submit)': 'onSubmit()'}, host: {'(submit)': 'onSubmit()', '(reset)': 'onReset()'},
exportAs: 'ngForm' exportAs: 'ngForm'
}) })
export class FormGroupDirective extends ControlContainer implements Form, export class FormGroupDirective extends ControlContainer implements Form,
@ -187,6 +187,8 @@ export class FormGroupDirective extends ControlContainer implements Form,
return false; return false;
} }
onReset(): void { this.form.reset(); }
/** @internal */ /** @internal */
_updateDomValue() { _updateDomValue() {
this.directives.forEach(dir => { this.directives.forEach(dir => {

View File

@ -49,8 +49,13 @@ export function setUpControl(control: FormControl, dir: NgControl): void {
control.markAsDirty(); control.markAsDirty();
}); });
// model -> view control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
control.registerOnChange((newValue: any) => dir.valueAccessor.writeValue(newValue)); // control -> view
dir.valueAccessor.writeValue(newValue);
// control -> ngModel
if (emitModelEvent) dir.viewToModelUpdate(newValue);
});
// touched // touched
dir.valueAccessor.registerOnTouched(() => control.markAsTouched()); dir.valueAccessor.registerOnTouched(() => control.markAsTouched());

View File

@ -83,6 +83,7 @@ export abstract class AbstractControl {
private _parent: FormGroup|FormArray; private _parent: FormGroup|FormArray;
private _asyncValidationSubscription: any; private _asyncValidationSubscription: any;
constructor(public validator: ValidatorFn, public asyncValidator: AsyncValidatorFn) {} constructor(public validator: ValidatorFn, public asyncValidator: AsyncValidatorFn) {}
get value(): any { return this._value; } get value(): any { return this._value; }
@ -140,6 +141,27 @@ export abstract class AbstractControl {
} }
} }
markAsPristine({onlySelf}: {onlySelf?: boolean} = {}): void {
this._pristine = true;
this._forEachChild((control: AbstractControl) => { control.markAsPristine({onlySelf: true}); });
if (isPresent(this._parent) && !onlySelf) {
this._parent._updatePristine({onlySelf: onlySelf});
}
}
markAsUntouched({onlySelf}: {onlySelf?: boolean} = {}): void {
this._touched = false;
this._forEachChild(
(control: AbstractControl) => { control.markAsUntouched({onlySelf: true}); });
if (isPresent(this._parent) && !onlySelf) {
this._parent._updateTouched({onlySelf: onlySelf});
}
}
markAsPending({onlySelf}: {onlySelf?: boolean} = {}): void { markAsPending({onlySelf}: {onlySelf?: boolean} = {}): void {
onlySelf = normalizeBool(onlySelf); onlySelf = normalizeBool(onlySelf);
this._status = PENDING; this._status = PENDING;
@ -153,6 +175,8 @@ export abstract class AbstractControl {
abstract updateValue(value: any, options?: Object): void; abstract updateValue(value: any, options?: Object): void;
abstract reset(value?: any, options?: Object): void;
updateValueAndValidity({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): updateValueAndValidity({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
void { void {
onlySelf = normalizeBool(onlySelf); onlySelf = normalizeBool(onlySelf);
@ -283,7 +307,43 @@ export abstract class AbstractControl {
abstract _updateValue(): void; abstract _updateValue(): void;
/** @internal */ /** @internal */
abstract _anyControlsHaveStatus(status: string): boolean; abstract _forEachChild(cb: Function): void;
/** @internal */
abstract _anyControls(condition: Function): boolean;
/** @internal */
_anyControlsHaveStatus(status: string): boolean {
return this._anyControls((control: AbstractControl) => control.status == status);
}
/** @internal */
_anyControlsDirty(): boolean {
return this._anyControls((control: AbstractControl) => control.dirty);
}
/** @internal */
_anyControlsTouched(): boolean {
return this._anyControls((control: AbstractControl) => control.touched);
}
/** @internal */
_updatePristine({onlySelf}: {onlySelf?: boolean} = {}): void {
this._pristine = !this._anyControlsDirty();
if (isPresent(this._parent) && !onlySelf) {
this._parent._updatePristine({onlySelf: onlySelf});
}
}
/** @internal */
_updateTouched({onlySelf}: {onlySelf?: boolean} = {}): void {
this._touched = this._anyControlsTouched();
if (isPresent(this._parent) && !onlySelf) {
this._parent._updateTouched({onlySelf: onlySelf});
}
}
} }
/** /**
@ -328,20 +388,32 @@ export class FormControl extends AbstractControl {
* If `emitModelToViewChange` is `true`, the view will be notified about the new value * If `emitModelToViewChange` is `true`, the view will be notified about the new value
* via an `onChange` event. This is the default behavior if `emitModelToViewChange` is not * via an `onChange` event. This is the default behavior if `emitModelToViewChange` is not
* specified. * specified.
*
* If `emitViewToModelChange` is `true`, an ngModelChange event will be fired to update the
* model. This is the default behavior if `emitViewToModelChange` is not specified.
*/ */
updateValue(value: any, {onlySelf, emitEvent, emitModelToViewChange}: { updateValue(value: any, {onlySelf, emitEvent, emitModelToViewChange, emitViewToModelChange}: {
onlySelf?: boolean, onlySelf?: boolean,
emitEvent?: boolean, emitEvent?: boolean,
emitModelToViewChange?: boolean emitModelToViewChange?: boolean,
emitViewToModelChange?: boolean
} = {}): void { } = {}): void {
emitModelToViewChange = isPresent(emitModelToViewChange) ? emitModelToViewChange : true; emitModelToViewChange = isPresent(emitModelToViewChange) ? emitModelToViewChange : true;
emitViewToModelChange = isPresent(emitViewToModelChange) ? emitViewToModelChange : true;
this._value = value; this._value = value;
if (this._onChange.length && emitModelToViewChange) { if (this._onChange.length && emitModelToViewChange) {
this._onChange.forEach((changeFn) => changeFn(this._value)); this._onChange.forEach((changeFn) => changeFn(this._value, emitViewToModelChange));
} }
this.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent}); this.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
} }
reset(value: any = null, {onlySelf}: {onlySelf?: boolean} = {}): void {
this.updateValue(value, {onlySelf: onlySelf});
this.markAsPristine({onlySelf: onlySelf});
this.markAsUntouched({onlySelf: onlySelf});
}
/** /**
* @internal * @internal
*/ */
@ -350,12 +422,17 @@ export class FormControl extends AbstractControl {
/** /**
* @internal * @internal
*/ */
_anyControlsHaveStatus(status: string): boolean { return false; } _anyControls(condition: Function): boolean { return false; }
/** /**
* Register a listener for change events. * Register a listener for change events.
*/ */
registerOnChange(fn: Function): void { this._onChange.push(fn); } registerOnChange(fn: Function): void { this._onChange.push(fn); }
/**
* @internal
*/
_forEachChild(cb: Function): void {}
} }
/** /**
@ -445,6 +522,15 @@ export class FormGroup extends AbstractControl {
this.updateValueAndValidity({onlySelf: onlySelf}); this.updateValueAndValidity({onlySelf: onlySelf});
} }
reset(value: any = {}, {onlySelf}: {onlySelf?: boolean} = {}): void {
this._forEachChild((control: AbstractControl, name: string) => {
control.reset(value[name], {onlySelf: true});
});
this.updateValueAndValidity({onlySelf: onlySelf});
this._updatePristine({onlySelf: onlySelf});
this._updateTouched({onlySelf: onlySelf});
}
/** @internal */ /** @internal */
_throwIfControlMissing(name: string): void { _throwIfControlMissing(name: string): void {
if (!this.controls[name]) { if (!this.controls[name]) {
@ -452,20 +538,22 @@ export class FormGroup extends AbstractControl {
} }
} }
/** @internal */
_forEachChild(cb: Function): void { StringMapWrapper.forEach(this.controls, cb); }
/** @internal */ /** @internal */
_setParentForControls() { _setParentForControls() {
StringMapWrapper.forEach( this._forEachChild((control: AbstractControl, name: string) => { control.setParent(this); });
this.controls, (control: AbstractControl, name: string) => { control.setParent(this); });
} }
/** @internal */ /** @internal */
_updateValue() { this._value = this._reduceValue(); } _updateValue() { this._value = this._reduceValue(); }
/** @internal */ /** @internal */
_anyControlsHaveStatus(status: string): boolean { _anyControls(condition: Function): boolean {
var res = false; var res = false;
StringMapWrapper.forEach(this.controls, (control: AbstractControl, name: string) => { this._forEachChild((control: AbstractControl, name: string) => {
res = res || (this.contains(name) && control.status == status); res = res || (this.contains(name) && condition(control));
}); });
return res; return res;
} }
@ -482,7 +570,7 @@ export class FormGroup extends AbstractControl {
/** @internal */ /** @internal */
_reduceChildren(initValue: any, fn: Function) { _reduceChildren(initValue: any, fn: Function) {
var res = initValue; var res = initValue;
StringMapWrapper.forEach(this.controls, (control: AbstractControl, name: string) => { this._forEachChild((control: AbstractControl, name: string) => {
if (this._included(name)) { if (this._included(name)) {
res = fn(res, control, name); res = fn(res, control, name);
} }
@ -575,6 +663,15 @@ export class FormArray extends AbstractControl {
this.updateValueAndValidity({onlySelf: onlySelf}); this.updateValueAndValidity({onlySelf: onlySelf});
} }
reset(value: any = [], {onlySelf}: {onlySelf?: boolean} = {}): void {
this._forEachChild((control: AbstractControl, index: number) => {
control.reset(value[index], {onlySelf: true});
});
this.updateValueAndValidity({onlySelf: onlySelf});
this._updatePristine({onlySelf: onlySelf});
this._updateTouched({onlySelf: onlySelf});
}
/** @internal */ /** @internal */
_throwIfControlMissing(index: number): void { _throwIfControlMissing(index: number): void {
if (!this.at(index)) { if (!this.at(index)) {
@ -582,17 +679,21 @@ export class FormArray extends AbstractControl {
} }
} }
/** @internal */
_forEachChild(cb: Function): void {
this.controls.forEach((control: AbstractControl, index: number) => { cb(control, index); });
}
/** @internal */ /** @internal */
_updateValue(): void { this._value = this.controls.map((control) => control.value); } _updateValue(): void { this._value = this.controls.map((control) => control.value); }
/** @internal */ /** @internal */
_anyControlsHaveStatus(status: string): boolean { _anyControls(condition: Function): boolean {
return this.controls.some(c => c.status == status); return this.controls.some((control: AbstractControl) => condition(control));
} }
/** @internal */ /** @internal */
_setParentForControls(): void { _setParentForControls(): void {
this.controls.forEach((control) => { control.setParent(this); }); this._forEachChild((control: AbstractControl) => { control.setParent(this); });
} }
} }

View File

@ -278,6 +278,59 @@ export function main() {
}); });
})); }));
it('should clear value in UI when form resets programmatically',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const login = new FormControl('oldValue');
const form = new FormGroup({'login': login});
const t = `<div [formGroup]="form">
<input type="text" formControlName="login">
</div>`;
tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => {
fixture.debugElement.componentInstance.form = form;
fixture.detectChanges();
login.updateValue('new value');
const loginEl = fixture.debugElement.query(By.css('input')).nativeElement;
expect(loginEl.value).toBe('new value');
form.reset();
expect(loginEl.value).toBe('');
async.done();
});
}));
it('should set value in UI when form resets to that value programmatically',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const login = new FormControl('oldValue');
const form = new FormGroup({'login': login});
const t = `<div [formGroup]="form">
<input type="text" formControlName="login">
</div>`;
tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => {
fixture.debugElement.componentInstance.form = form;
fixture.detectChanges();
login.updateValue('new value');
const loginEl = fixture.debugElement.query(By.css('input')).nativeElement;
expect(loginEl.value).toBe('new value');
form.reset({'login': 'oldValue'});
expect(loginEl.value).toBe('oldValue');
async.done();
});
}));
it('should support form arrays', it('should support form arrays',
fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]); const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
@ -1283,6 +1336,32 @@ export function main() {
expect(fixture.debugElement.componentInstance.name).toEqual('updated'); expect(fixture.debugElement.componentInstance.name).toEqual('updated');
}))); })));
it('should reset the form to empty when reset button is clicked',
fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
const t = `
<form>
<input name="name" [(ngModel)]="name">
</form>
`;
const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8);
tick();
fixture.debugElement.componentInstance.name = 'should be cleared';
fixture.detectChanges();
tick();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const formEl = fixture.debugElement.query(By.css('form'));
dispatchEvent(formEl.nativeElement, 'reset');
fixture.detectChanges();
tick();
expect(fixture.debugElement.componentInstance.name).toBe(null);
expect(form.value.name).toEqual(null);
})));
it('should emit valueChanges and statusChanges on init', it('should emit valueChanges and statusChanges on init',
fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
const t = `<form> const t = `<form>

View File

@ -312,6 +312,130 @@ export function main() {
})); }));
}); });
describe('reset()', () => {
let c: FormControl;
beforeEach(() => { c = new FormControl('initial value'); });
it('should restore the initial value of the control if passed', () => {
c.updateValue('new value');
expect(c.value).toBe('new value');
c.reset('initial value');
expect(c.value).toBe('initial value');
});
it('should clear the control value if no value is passed', () => {
c.updateValue('new value');
expect(c.value).toBe('new value');
c.reset();
expect(c.value).toBe(null);
});
it('should update the value of any parent controls with passed value', () => {
const g = new FormGroup({'one': c});
c.updateValue('new value');
expect(g.value).toEqual({'one': 'new value'});
c.reset('initial value');
expect(g.value).toEqual({'one': 'initial value'});
});
it('should update the value of any parent controls with null value', () => {
const g = new FormGroup({'one': c});
c.updateValue('new value');
expect(g.value).toEqual({'one': 'new value'});
c.reset();
expect(g.value).toEqual({'one': null});
});
it('should mark the control as pristine', () => {
c.markAsDirty();
expect(c.pristine).toBe(false);
c.reset();
expect(c.pristine).toBe(true);
});
it('should set the parent pristine state if all pristine', () => {
const g = new FormGroup({'one': c});
c.markAsDirty();
expect(g.pristine).toBe(false);
c.reset();
expect(g.pristine).toBe(true);
});
it('should not set the parent pristine state if it has other dirty controls', () => {
const c2 = new FormControl('two');
const g = new FormGroup({'one': c, 'two': c2});
c.markAsDirty();
c2.markAsDirty();
c.reset();
expect(g.pristine).toBe(false);
});
it('should mark the control as untouched', () => {
c.markAsTouched();
expect(c.untouched).toBe(false);
c.reset();
expect(c.untouched).toBe(true);
});
it('should set the parent untouched state if all untouched', () => {
const g = new FormGroup({'one': c});
c.markAsTouched();
expect(g.untouched).toBe(false);
c.reset();
expect(g.untouched).toBe(true);
});
it('should not set the parent untouched state if other touched controls', () => {
const c2 = new FormControl('two');
const g = new FormGroup({'one': c, 'two': c2});
c.markAsTouched();
c2.markAsTouched();
c.reset();
expect(g.untouched).toBe(false);
});
describe('reset() events', () => {
let g: FormGroup, c2: FormControl, logger: any[];
beforeEach(() => {
c2 = new FormControl('two');
g = new FormGroup({'one': c, 'two': c2});
logger = [];
});
it('should emit one valueChange event per reset control', () => {
g.valueChanges.subscribe(() => logger.push('group'));
c.valueChanges.subscribe(() => logger.push('control1'));
c2.valueChanges.subscribe(() => logger.push('control2'));
c.reset();
expect(logger).toEqual(['control1', 'group']);
});
it('should emit one statusChange event per reset control', () => {
g.statusChanges.subscribe(() => logger.push('group'));
c.statusChanges.subscribe(() => logger.push('control1'));
c2.statusChanges.subscribe(() => logger.push('control2'));
c.reset();
expect(logger).toEqual(['control1', 'group']);
});
});
});
describe('valueChanges & statusChanges', () => { describe('valueChanges & statusChanges', () => {
var c: any /** TODO #9100 */; var c: any /** TODO #9100 */;
@ -634,6 +758,179 @@ export function main() {
}); });
}); });
describe('reset()', () => {
let c: FormControl, c2: FormControl, g: FormGroup;
beforeEach(() => {
c = new FormControl('initial value');
c2 = new FormControl('');
g = new FormGroup({'one': c, 'two': c2});
});
it('should set its own value if value passed', () => {
g.updateValue({'one': 'new value', 'two': 'new value'});
g.reset({'one': 'initial value', 'two': ''});
expect(g.value).toEqual({'one': 'initial value', 'two': ''});
});
it('should clear its own value if no value passed', () => {
g.updateValue({'one': 'new value', 'two': 'new value'});
g.reset();
expect(g.value).toEqual({'one': null, 'two': null});
});
it('should set the value of each of its child controls if value passed', () => {
g.updateValue({'one': 'new value', 'two': 'new value'});
g.reset({'one': 'initial value', 'two': ''});
expect(c.value).toBe('initial value');
expect(c2.value).toBe('');
});
it('should clear the value of each of its child controls if no value passed', () => {
g.updateValue({'one': 'new value', 'two': 'new value'});
g.reset();
expect(c.value).toBe(null);
expect(c2.value).toBe(null);
});
it('should set the value of its parent if value passed', () => {
const form = new FormGroup({'g': g});
g.updateValue({'one': 'new value', 'two': 'new value'});
g.reset({'one': 'initial value', 'two': ''});
expect(form.value).toEqual({'g': {'one': 'initial value', 'two': ''}});
});
it('should clear the value of its parent if no value passed', () => {
const form = new FormGroup({'g': g});
g.updateValue({'one': 'new value', 'two': 'new value'});
g.reset();
expect(form.value).toEqual({'g': {'one': null, 'two': null}});
});
it('should mark itself as pristine', () => {
g.markAsDirty();
expect(g.pristine).toBe(false);
g.reset();
expect(g.pristine).toBe(true);
});
it('should mark all child controls as pristine', () => {
c.markAsDirty();
c2.markAsDirty();
expect(c.pristine).toBe(false);
expect(c2.pristine).toBe(false);
g.reset();
expect(c.pristine).toBe(true);
expect(c2.pristine).toBe(true);
});
it('should mark the parent as pristine if all siblings pristine', () => {
const c3 = new FormControl('');
const form = new FormGroup({'g': g, 'c3': c3});
g.markAsDirty();
expect(form.pristine).toBe(false);
g.reset();
expect(form.pristine).toBe(true);
});
it('should not mark the parent pristine if any dirty siblings', () => {
const c3 = new FormControl('');
const form = new FormGroup({'g': g, 'c3': c3});
g.markAsDirty();
c3.markAsDirty();
expect(form.pristine).toBe(false);
g.reset();
expect(form.pristine).toBe(false);
});
it('should mark itself as untouched', () => {
g.markAsTouched();
expect(g.untouched).toBe(false);
g.reset();
expect(g.untouched).toBe(true);
});
it('should mark all child controls as untouched', () => {
c.markAsTouched();
c2.markAsTouched();
expect(c.untouched).toBe(false);
expect(c2.untouched).toBe(false);
g.reset();
expect(c.untouched).toBe(true);
expect(c2.untouched).toBe(true);
});
it('should mark the parent untouched if all siblings untouched', () => {
const c3 = new FormControl('');
const form = new FormGroup({'g': g, 'c3': c3});
g.markAsTouched();
expect(form.untouched).toBe(false);
g.reset();
expect(form.untouched).toBe(true);
});
it('should not mark the parent untouched if any touched siblings', () => {
const c3 = new FormControl('');
const form = new FormGroup({'g': g, 'c3': c3});
g.markAsTouched();
c3.markAsTouched();
expect(form.untouched).toBe(false);
g.reset();
expect(form.untouched).toBe(false);
});
describe('reset() events', () => {
let form: FormGroup, c3: FormControl, logger: any[];
beforeEach(() => {
c3 = new FormControl('');
form = new FormGroup({'g': g, 'c3': c3});
logger = [];
});
it('should emit one valueChange event per reset control', () => {
form.valueChanges.subscribe(() => logger.push('form'));
g.valueChanges.subscribe(() => logger.push('group'));
c.valueChanges.subscribe(() => logger.push('control1'));
c2.valueChanges.subscribe(() => logger.push('control2'));
c3.valueChanges.subscribe(() => logger.push('control3'));
g.reset();
expect(logger).toEqual(['control1', 'control2', 'group', 'form']);
});
it('should emit one statusChange event per reset control', () => {
form.statusChanges.subscribe(() => logger.push('form'));
g.statusChanges.subscribe(() => logger.push('group'));
c.statusChanges.subscribe(() => logger.push('control1'));
c2.statusChanges.subscribe(() => logger.push('control2'));
c3.statusChanges.subscribe(() => logger.push('control3'));
g.reset();
expect(logger).toEqual(['control1', 'control2', 'group', 'form']);
});
});
});
describe('optional components', () => { describe('optional components', () => {
describe('contains', () => { describe('contains', () => {
var group: any /** TODO #9100 */; var group: any /** TODO #9100 */;
@ -982,6 +1279,179 @@ export function main() {
}); });
}); });
describe('reset()', () => {
let c: FormControl, c2: FormControl, a: FormArray;
beforeEach(() => {
c = new FormControl('initial value');
c2 = new FormControl('');
a = new FormArray([c, c2]);
});
it('should set its own value if value passed', () => {
a.updateValue(['new value', 'new value']);
a.reset(['initial value', '']);
expect(a.value).toEqual(['initial value', '']);
});
it('should clear its own value if no value passed', () => {
a.updateValue(['new value', 'new value']);
a.reset();
expect(a.value).toEqual([null, null]);
});
it('should set the value of each of its child controls if value passed', () => {
a.updateValue(['new value', 'new value']);
a.reset(['initial value', '']);
expect(c.value).toBe('initial value');
expect(c2.value).toBe('');
});
it('should clear the value of each of its child controls if no value', () => {
a.updateValue(['new value', 'new value']);
a.reset();
expect(c.value).toBe(null);
expect(c2.value).toBe(null);
});
it('should set the value of its parent if value passed', () => {
const form = new FormGroup({'a': a});
a.updateValue(['new value', 'new value']);
a.reset(['initial value', '']);
expect(form.value).toEqual({'a': ['initial value', '']});
});
it('should clear the value of its parent if no value passed', () => {
const form = new FormGroup({'a': a});
a.updateValue(['new value', 'new value']);
a.reset();
expect(form.value).toEqual({'a': [null, null]});
});
it('should mark itself as pristine', () => {
a.markAsDirty();
expect(a.pristine).toBe(false);
a.reset();
expect(a.pristine).toBe(true);
});
it('should mark all child controls as pristine', () => {
c.markAsDirty();
c2.markAsDirty();
expect(c.pristine).toBe(false);
expect(c2.pristine).toBe(false);
a.reset();
expect(c.pristine).toBe(true);
expect(c2.pristine).toBe(true);
});
it('should mark the parent as pristine if all siblings pristine', () => {
const c3 = new FormControl('');
const form = new FormGroup({'a': a, 'c3': c3});
a.markAsDirty();
expect(form.pristine).toBe(false);
a.reset();
expect(form.pristine).toBe(true);
});
it('should not mark the parent pristine if any dirty siblings', () => {
const c3 = new FormControl('');
const form = new FormGroup({'a': a, 'c3': c3});
a.markAsDirty();
c3.markAsDirty();
expect(form.pristine).toBe(false);
a.reset();
expect(form.pristine).toBe(false);
});
it('should mark itself as untouched', () => {
a.markAsTouched();
expect(a.untouched).toBe(false);
a.reset();
expect(a.untouched).toBe(true);
});
it('should mark all child controls as untouched', () => {
c.markAsTouched();
c2.markAsTouched();
expect(c.untouched).toBe(false);
expect(c2.untouched).toBe(false);
a.reset();
expect(c.untouched).toBe(true);
expect(c2.untouched).toBe(true);
});
it('should mark the parent untouched if all siblings untouched', () => {
const c3 = new FormControl('');
const form = new FormGroup({'a': a, 'c3': c3});
a.markAsTouched();
expect(form.untouched).toBe(false);
a.reset();
expect(form.untouched).toBe(true);
});
it('should not mark the parent untouched if any touched siblings', () => {
const c3 = new FormControl('');
const form = new FormGroup({'a': a, 'c3': c3});
a.markAsTouched();
c3.markAsTouched();
expect(form.untouched).toBe(false);
a.reset();
expect(form.untouched).toBe(false);
});
describe('reset() events', () => {
let form: FormGroup, c3: FormControl, logger: any[];
beforeEach(() => {
c3 = new FormControl('');
form = new FormGroup({'a': a, 'c3': c3});
logger = [];
});
it('should emit one valueChange event per reset control', () => {
form.valueChanges.subscribe(() => logger.push('form'));
a.valueChanges.subscribe(() => logger.push('array'));
c.valueChanges.subscribe(() => logger.push('control1'));
c2.valueChanges.subscribe(() => logger.push('control2'));
c3.valueChanges.subscribe(() => logger.push('control3'));
a.reset();
expect(logger).toEqual(['control1', 'control2', 'array', 'form']);
});
it('should emit one statusChange event per reset control', () => {
form.statusChanges.subscribe(() => logger.push('form'));
a.statusChanges.subscribe(() => logger.push('array'));
c.statusChanges.subscribe(() => logger.push('control1'));
c2.statusChanges.subscribe(() => logger.push('control2'));
c3.statusChanges.subscribe(() => logger.push('control3'));
a.reset();
expect(logger).toEqual(['control1', 'control2', 'array', 'form']);
});
});
});
describe('errors', () => { describe('errors', () => {
it('should run the validator when the value changes', () => { it('should run the validator when the value changes', () => {
var simpleValidator = (c: any /** TODO #9100 */) => var simpleValidator = (c: any /** TODO #9100 */) =>

View File

@ -28,9 +28,16 @@ export declare abstract class AbstractControl {
markAsPending({onlySelf}?: { markAsPending({onlySelf}?: {
onlySelf?: boolean; onlySelf?: boolean;
}): void; }): void;
markAsPristine({onlySelf}?: {
onlySelf?: boolean;
}): void;
markAsTouched({onlySelf}?: { markAsTouched({onlySelf}?: {
onlySelf?: boolean; onlySelf?: boolean;
}): void; }): void;
markAsUntouched({onlySelf}?: {
onlySelf?: boolean;
}): void;
abstract reset(value?: any, options?: Object): void;
setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[]): void; setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[]): void;
setErrors(errors: { setErrors(errors: {
[key: string]: any; [key: string]: any;
@ -61,6 +68,7 @@ export declare abstract class AbstractControlDirective {
valid: boolean; valid: boolean;
value: any; value: any;
valueChanges: Observable<any>; valueChanges: Observable<any>;
reset(value?: any): void;
} }
/** @experimental */ /** @experimental */
@ -131,6 +139,9 @@ export declare class FormArray extends AbstractControl {
insert(index: number, control: AbstractControl): void; insert(index: number, control: AbstractControl): void;
push(control: AbstractControl): void; push(control: AbstractControl): void;
removeAt(index: number): void; removeAt(index: number): void;
reset(value?: any, {onlySelf}?: {
onlySelf?: boolean;
}): void;
updateValue(value: any[], {onlySelf}?: { updateValue(value: any[], {onlySelf}?: {
onlySelf?: boolean; onlySelf?: boolean;
}): void; }): void;
@ -164,10 +175,14 @@ export declare class FormBuilder {
export declare class FormControl extends AbstractControl { export declare class FormControl extends AbstractControl {
constructor(value?: any, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]); constructor(value?: any, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]);
registerOnChange(fn: Function): void; registerOnChange(fn: Function): void;
updateValue(value: any, {onlySelf, emitEvent, emitModelToViewChange}?: { reset(value?: any, {onlySelf}?: {
onlySelf?: boolean;
}): void;
updateValue(value: any, {onlySelf, emitEvent, emitModelToViewChange, emitViewToModelChange}?: {
onlySelf?: boolean; onlySelf?: boolean;
emitEvent?: boolean; emitEvent?: boolean;
emitModelToViewChange?: boolean; emitModelToViewChange?: boolean;
emitViewToModelChange?: boolean;
}): void; }): void;
} }
@ -218,6 +233,9 @@ export declare class FormGroup extends AbstractControl {
include(controlName: string): void; include(controlName: string): void;
registerControl(name: string, control: AbstractControl): AbstractControl; registerControl(name: string, control: AbstractControl): AbstractControl;
removeControl(name: string): void; removeControl(name: string): void;
reset(value?: any, {onlySelf}?: {
onlySelf?: boolean;
}): void;
updateValue(value: { updateValue(value: {
[key: string]: any; [key: string]: any;
}, {onlySelf}?: { }, {onlySelf}?: {
@ -242,6 +260,7 @@ export declare class FormGroupDirective extends ControlContainer implements Form
getFormArray(dir: FormArrayName): FormArray; getFormArray(dir: FormArrayName): FormArray;
getFormGroup(dir: FormGroupName): FormGroup; getFormGroup(dir: FormGroupName): FormGroup;
ngOnChanges(changes: SimpleChanges): void; ngOnChanges(changes: SimpleChanges): void;
onReset(): void;
onSubmit(): boolean; onSubmit(): boolean;
removeControl(dir: NgControl): void; removeControl(dir: NgControl): void;
removeFormArray(dir: FormArrayName): void; removeFormArray(dir: FormArrayName): void;
@ -320,6 +339,7 @@ export declare class NgForm extends ControlContainer implements Form {
addFormGroup(dir: NgModelGroup): void; addFormGroup(dir: NgModelGroup): void;
getControl(dir: NgModel): FormControl; getControl(dir: NgModel): FormControl;
getFormGroup(dir: NgModelGroup): FormGroup; getFormGroup(dir: NgModelGroup): FormGroup;
onReset(): void;
onSubmit(): boolean; onSubmit(): boolean;
removeControl(dir: NgModel): void; removeControl(dir: NgModel): void;
removeFormGroup(dir: NgModelGroup): void; removeFormGroup(dir: NgModelGroup): void;