Skip to content

Commit ed4826b

Browse files
committed
feat(forms): Implement a way to manually set errors on a control
Example: var login = new Control("someLogin"); c.setErrors({"notUnique": true}); expect(c.valid).toEqual(false); expect(c.errors).toEqual({"notUnique": true}); c.updateValue("newLogin"); expect(c.valid).toEqual(true); BREAKING CHANGE: Before: ControlGroup.errors and ControlArray.errors returned a reduced value of their children controls' errors. After: ControlGroup.errors and ControlArray.errors return the errors of the group and array. And ControlGroup.controlsErrors and ControlArray.controlsErrors return the reduce value of their children controls' errors. Closes #4917
1 parent 689ded5 commit ed4826b

File tree

11 files changed

+328
-200
lines changed

11 files changed

+328
-200
lines changed

modules/angular2/src/core/forms/directives/abstract_control_directive.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export class AbstractControlDirective {
1212
return isPresent(this.control) ? this.control.errors : null;
1313
}
1414

15+
get controlsErrors(): any { return isPresent(this.control) ? this.control.controlsErrors : null; }
16+
1517
get pristine(): boolean { return isPresent(this.control) ? this.control.pristine : null; }
1618

1719
get dirty(): boolean { return isPresent(this.control) ? this.control.dirty : null; }

modules/angular2/src/core/forms/directives/ng_form.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class NgForm extends ControlContainer implements Form {
104104
var ctrl = new Control();
105105
setUpControl(ctrl, dir);
106106
container.addControl(dir.name, ctrl);
107-
ctrl.updateValidity();
107+
ctrl.updateValueAndValidity({emitEvent: false});
108108
});
109109
}
110110

@@ -115,7 +115,7 @@ export class NgForm extends ControlContainer implements Form {
115115
var container = this._findContainer(dir.path);
116116
if (isPresent(container)) {
117117
container.removeControl(dir.name);
118-
container.updateValidity();
118+
container.updateValueAndValidity({emitEvent: false});
119119
}
120120
});
121121
}
@@ -125,7 +125,7 @@ export class NgForm extends ControlContainer implements Form {
125125
var container = this._findContainer(dir.path);
126126
var group = new ControlGroup({});
127127
container.addControl(dir.name, group);
128-
group.updateValidity();
128+
group.updateValueAndValidity({emitEvent: false});
129129
});
130130
}
131131

@@ -134,7 +134,7 @@ export class NgForm extends ControlContainer implements Form {
134134
var container = this._findContainer(dir.path);
135135
if (isPresent(container)) {
136136
container.removeControl(dir.name);
137-
container.updateValidity();
137+
container.updateValueAndValidity({emitEvent: false});
138138
}
139139
});
140140
}

modules/angular2/src/core/forms/directives/ng_form_control.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export class NgFormControl extends NgControl implements OnChanges {
8585
onChanges(changes: {[key: string]: SimpleChange}): void {
8686
if (this._isControlChanged(changes)) {
8787
setUpControl(this.form, this);
88-
this.form.updateValidity();
88+
this.form.updateValueAndValidity({emitEvent: false});
8989
}
9090
if (isPropertyUpdated(changes, this.viewModel)) {
9191
this.form.updateValue(this.model);

modules/angular2/src/core/forms/directives/ng_form_model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export class NgFormModel extends ControlContainer implements Form,
112112
addControl(dir: NgControl): void {
113113
var ctrl: any = this.form.find(dir.path);
114114
setUpControl(ctrl, dir);
115-
ctrl.updateValidity();
115+
ctrl.updateValueAndValidity({emitEvent: false});
116116
this.directives.push(dir);
117117
}
118118

modules/angular2/src/core/forms/directives/ng_model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class NgModel extends NgControl implements OnChanges {
6161
onChanges(changes: {[key: string]: SimpleChange}) {
6262
if (!this._added) {
6363
setUpControl(this._control, this);
64-
this._control.updateValidity();
64+
this._control.updateValueAndValidity({emitEvent: false});
6565
this._added = true;
6666
}
6767

modules/angular2/src/core/forms/model.ts

Lines changed: 90 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,20 @@ function _find(control: AbstractControl, path: Array<string | number>| string) {
4646
/**
4747
*
4848
*/
49-
export class AbstractControl {
49+
export abstract class AbstractControl {
5050
/** @internal */
5151
_value: any;
52-
/** @internal */
53-
_status: string;
54-
/** @internal */
55-
_errors: {[key: string]: any};
56-
/** @internal */
57-
_pristine: boolean = true;
58-
/** @internal */
59-
_touched: boolean = false;
60-
/** @internal */
61-
_parent: ControlGroup | ControlArray;
52+
6253
/** @internal */
6354
_valueChanges: EventEmitter;
6455

56+
private _status: string;
57+
private _errors: {[key: string]: any};
58+
private _controlsErrors: any;
59+
private _pristine: boolean = true;
60+
private _touched: boolean = false;
61+
private _parent: ControlGroup | ControlArray;
62+
6563
constructor(public validator: Function) {}
6664

6765
get value(): any { return this._value; }
@@ -70,8 +68,16 @@ export class AbstractControl {
7068

7169
get valid(): boolean { return this._status === VALID; }
7270

71+
/**
72+
* Returns the errors of this control.
73+
*/
7374
get errors(): {[key: string]: any} { return this._errors; }
7475

76+
/**
77+
* Returns the errors of the child controls.
78+
*/
79+
get controlsErrors(): any { return this._controlsErrors; }
80+
7581
get pristine(): boolean { return this._pristine; }
7682

7783
get dirty(): boolean { return !this.pristine; }
@@ -105,17 +111,6 @@ export class AbstractControl {
105111

106112
setParent(parent: ControlGroup | ControlArray): void { this._parent = parent; }
107113

108-
updateValidity({onlySelf}: {onlySelf?: boolean} = {}): void {
109-
onlySelf = normalizeBool(onlySelf);
110-
111-
this._errors = this.validator(this);
112-
this._status = isPresent(this._errors) ? INVALID : VALID;
113-
114-
if (isPresent(this._parent) && !onlySelf) {
115-
this._parent.updateValidity({onlySelf: onlySelf});
116-
}
117-
}
118-
119114
updateValueAndValidity({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
120115
void {
121116
onlySelf = normalizeBool(onlySelf);
@@ -124,7 +119,8 @@ export class AbstractControl {
124119
this._updateValue();
125120

126121
this._errors = this.validator(this);
127-
this._status = isPresent(this._errors) ? INVALID : VALID;
122+
this._controlsErrors = this._calculateControlsErrors();
123+
this._status = this._calculateStatus();
128124

129125
if (emitEvent) {
130126
ObservableWrapper.callNext(this._valueChanges, this._value);
@@ -135,6 +131,38 @@ export class AbstractControl {
135131
}
136132
}
137133

134+
/**
135+
* Sets errors on a control.
136+
*
137+
* This is used when validations are run not automatically, but manually by the user.
138+
*
139+
* Calling `setErrors` will also update the validity of the parent control.
140+
*
141+
* ## Usage
142+
*
143+
* ```
144+
* var login = new Control("someLogin");
145+
* login.setErrors({
146+
* "notUnique": true
147+
* });
148+
*
149+
* expect(login.valid).toEqual(false);
150+
* expect(login.errors).toEqual({"notUnique": true});
151+
*
152+
* login.updateValue("someOtherLogin");
153+
*
154+
* expect(login.valid).toEqual(true);
155+
* ```
156+
*/
157+
setErrors(errors: {[key: string]: any}): void {
158+
this._errors = errors;
159+
this._status = this._calculateStatus();
160+
161+
if (isPresent(this._parent)) {
162+
this._parent._updateControlsErrors();
163+
}
164+
}
165+
138166
find(path: Array<string | number>| string): AbstractControl { return _find(this, path); }
139167

140168
getError(errorCode: string, path: string[] = null): any {
@@ -151,7 +179,23 @@ export class AbstractControl {
151179
}
152180

153181
/** @internal */
154-
_updateValue(): void {}
182+
_updateControlsErrors(): void {
183+
this._controlsErrors = this._calculateControlsErrors();
184+
this._status = this._calculateStatus();
185+
186+
if (isPresent(this._parent)) {
187+
this._parent._updateControlsErrors();
188+
}
189+
}
190+
191+
private _calculateStatus(): string {
192+
return isPresent(this._errors) || isPresent(this._controlsErrors) ? INVALID : VALID;
193+
}
194+
195+
/** @internal */
196+
abstract _updateValue(): void;
197+
/** @internal */
198+
abstract _calculateControlsErrors(): any;
155199
}
156200

157201
/**
@@ -177,7 +221,7 @@ export class Control extends AbstractControl {
177221
constructor(value: any = null, validator: Function = Validators.nullValidator) {
178222
super(validator);
179223
this._value = value;
180-
this.updateValidity({onlySelf: true});
224+
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
181225
this._valueChanges = new EventEmitter();
182226
}
183227

@@ -203,6 +247,16 @@ export class Control extends AbstractControl {
203247
this.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
204248
}
205249

250+
/**
251+
* @internal
252+
*/
253+
_updateValue() {}
254+
255+
/**
256+
* @internal
257+
*/
258+
_calculateControlsErrors() { return null; }
259+
206260
/**
207261
* Register a listener for change events.
208262
*/
@@ -226,14 +280,14 @@ export class ControlGroup extends AbstractControl {
226280
private _optionals: {[key: string]: boolean};
227281

228282
constructor(public controls: {[key: string]: AbstractControl},
229-
optionals: {[key: string]: boolean} = null, validator: Function = Validators.group) {
283+
optionals: {[key: string]: boolean} = null,
284+
validator: Function = Validators.nullValidator) {
230285
super(validator);
231286
this._optionals = isPresent(optionals) ? optionals : {};
232287
this._valueChanges = new EventEmitter();
233288

234289
this._setParentForControls();
235-
this._value = this._reduceValue();
236-
this.updateValidity({onlySelf: true});
290+
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
237291
}
238292

239293
addControl(name: string, control: AbstractControl): void {
@@ -266,6 +320,9 @@ export class ControlGroup extends AbstractControl {
266320
/** @internal */
267321
_updateValue() { this._value = this._reduceValue(); }
268322

323+
/** @internal */
324+
_calculateControlsErrors() { return Validators.group(this); }
325+
269326
/** @internal */
270327
_reduceValue() {
271328
return this._reduceChildren({}, (acc, control, name) => {
@@ -314,14 +371,13 @@ export class ControlGroup extends AbstractControl {
314371
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
315372
*/
316373
export class ControlArray extends AbstractControl {
317-
constructor(public controls: AbstractControl[], validator: Function = Validators.array) {
374+
constructor(public controls: AbstractControl[], validator: Function = Validators.nullValidator) {
318375
super(validator);
319376

320377
this._valueChanges = new EventEmitter();
321378

322379
this._setParentForControls();
323-
this._updateValue();
324-
this.updateValidity({onlySelf: true});
380+
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
325381
}
326382

327383
/**
@@ -363,6 +419,9 @@ export class ControlArray extends AbstractControl {
363419
/** @internal */
364420
_updateValue(): void { this._value = this.controls.map((control) => control.value); }
365421

422+
/** @internal */
423+
_calculateControlsErrors() { return Validators.array(this); }
424+
366425
/** @internal */
367426
_setParentForControls(): void {
368427
this.controls.forEach((control) => { control.setParent(this); });

modules/angular2/src/core/forms/validators.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ export class Validators {
6262
res[name] = control.errors;
6363
}
6464
});
65-
return StringMapWrapper.isEmpty(res) ? null : {'controls': res};
65+
return StringMapWrapper.isEmpty(res) ? null : res;
6666
}
6767

68-
static array(array: modelModule.ControlArray): {[key: string]: any} {
68+
static array(array: modelModule.ControlArray): any[] {
6969
var res: any[] = [];
7070
var anyErrors: boolean = false;
7171
array.controls.forEach((control) => {
@@ -74,6 +74,6 @@ export class Validators {
7474
anyErrors = true;
7575
}
7676
});
77-
return anyErrors ? {'controls': res} : null;
77+
return anyErrors ? res : null;
7878
}
7979
}

modules/angular2/test/core/forms/form_builder_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function main() {
5353
it("should use default validators when no validators are provided", () => {
5454
var g = b.group({"login": "some value"});
5555
expect(g.controls["login"].validator).toBe(Validators.nullValidator);
56-
expect(g.validator).toBe(Validators.group);
56+
expect(g.validator).toBe(Validators.nullValidator);
5757
});
5858

5959
it("should create control arrays", () => {

0 commit comments

Comments
 (0)