Skip to content

Commit

Permalink
feat(forms): add ability to reset forms (#9974)
Browse files Browse the repository at this point in the history
Closes #4914
Closes #4933
  • Loading branch information
kara committed Jul 12, 2016
1 parent 806a254 commit da8eb9f
Show file tree
Hide file tree
Showing 9 changed files with 705 additions and 23 deletions.
Expand Up @@ -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);
}
} }
6 changes: 3 additions & 3 deletions modules/@angular/forms/src/directives/ng_form.ts
Expand Up @@ -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'
}) })
Expand Down Expand Up @@ -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();
Expand Down
3 changes: 2 additions & 1 deletion modules/@angular/forms/src/directives/ng_model.ts
Expand Up @@ -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}); });
} }
} }
Expand Up @@ -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,
Expand Down Expand Up @@ -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 => {
Expand Down
9 changes: 7 additions & 2 deletions modules/@angular/forms/src/directives/shared.ts
Expand Up @@ -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());
Expand Down
131 changes: 116 additions & 15 deletions modules/@angular/forms/src/model.ts
Expand Up @@ -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; }
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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});
}
}
} }


/** /**
Expand Down Expand Up @@ -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
*/ */
Expand All @@ -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 {}
} }


/** /**
Expand Down Expand Up @@ -445,27 +522,38 @@ 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]) {
throw new BaseException(`Cannot find form control with name: ${name}.`); throw new BaseException(`Cannot find form control with name: ${name}.`);
} }
} }


/** @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;
} }
Expand All @@ -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);
} }
Expand Down Expand Up @@ -575,24 +663,37 @@ 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)) {
throw new BaseException(`Cannot find form control at index ${index}`); throw new BaseException(`Cannot find form control at index ${index}`);
} }
} }


/** @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); });
} }
} }

0 comments on commit da8eb9f

Please sign in to comment.