Skip to content

Commit

Permalink
feat(forms): add hasError and getError to AbstractControlDirective
Browse files Browse the repository at this point in the history
Allows cleaner expressions in template-driven forms.

Before:

    <label>Username</label><input name="username" ngModel required #username="ngModel">
    <div *ngIf="username.dirty && username.control.hasError('required')">Username is required</div>

After:

    <label>Username</label><input name="username" ngModel required #username="ngModel">
    <div *ngIf="username.dirty && username.hasError('required')">Username is required</div>

Fixes angular#7255
  • Loading branch information
cexbrayat committed Oct 10, 2016
1 parent 36bc2ff commit dfa66b7
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,12 @@ export abstract class AbstractControlDirective {
reset(value: any = undefined): void {
if (isPresent(this.control)) this.control.reset(value);
}

hasError(errorCode: string, path: string[] = null): boolean {
return isPresent(this.control) ? this.control.hasError(errorCode, path) : false;
}

getError(errorCode: string, path: string[] = null): any {
return isPresent(this.control) ? this.control.getError(errorCode, path) : null;
}
}
148 changes: 59 additions & 89 deletions modules/@angular/forms/test/directives_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import {SimpleChange} from '@angular/core/src/change_detection';
import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
import {CheckboxControlValueAccessor, ControlValueAccessor, DefaultValueAccessor, FormArray, FormArrayName, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormGroupName, NgControl, NgForm, NgModel, NgModelGroup, SelectControlValueAccessor, SelectMultipleControlValueAccessor, Validator, Validators} from '@angular/forms';
import {CheckboxControlValueAccessor, ControlValueAccessor, DefaultValueAccessor, FormArray, FormArrayName, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormGroupName, NgControl, NgForm, NgModel, NgModelGroup, SelectControlValueAccessor, SelectMultipleControlValueAccessor, Validator, Validators, AbstractControlDirective, AbstractControl} from '@angular/forms';
import {composeValidators, selectValueAccessor} from '@angular/forms/src/directives/shared';

import {SpyNgControl, SpyValueAccessor} from './spies';
Expand All @@ -27,6 +27,13 @@ class CustomValidatorDirective implements Validator {
validate(c: FormControl): {[key: string]: any} { return {'custom': true}; }
}

class DummyControlDirective extends AbstractControlDirective {
constructor(private _control: AbstractControl) {
super();
};
get control(): any { return this._control; }
}

function asyncValidator(expected: any /** TODO #9100 */, timeout = 0) {
return (c: any /** TODO #9100 */) => {
var resolve: (result: any) => void;
Expand All @@ -42,6 +49,42 @@ function asyncValidator(expected: any /** TODO #9100 */, timeout = 0) {
}

export function main() {
describe('Directives that extend AbstractControlDirective', () => {

let control: FormControl;
let controlDirective: DummyControlDirective;

beforeEach(() => {
control = new FormControl();
controlDirective = new DummyControlDirective(control);
});

it('should reexport control properties', () => {
expect(controlDirective.control).toBe(control);
expect(controlDirective.value).toBe(control.value);
expect(controlDirective.valid).toBe(control.valid);
expect(controlDirective.invalid).toBe(control.invalid);
expect(controlDirective.pending).toBe(control.pending);
expect(controlDirective.errors).toBe(control.errors);
expect(controlDirective.pristine).toBe(control.pristine);
expect(controlDirective.dirty).toBe(control.dirty);
expect(controlDirective.touched).toBe(control.touched);
expect(controlDirective.untouched).toBe(control.untouched);
expect(controlDirective.statusChanges).toBe(control.statusChanges);
expect(controlDirective.valueChanges).toBe(control.valueChanges);
});

it('should reexport control methods', () => {
expect(controlDirective.hasError('required')).toBe(control.hasError('required'));
expect(controlDirective.getError('required')).toBe(control.getError('required'));

control.setErrors({required: true});
expect(controlDirective.hasError('required')).toBe(control.hasError('required'));
expect(controlDirective.getError('required')).toBe(control.getError('required'));
});

});

describe('Form Directives', () => {
var defaultAccessor: DefaultValueAccessor;

Expand Down Expand Up @@ -143,19 +186,8 @@ export function main() {
loginControlDir.valueAccessor = new DummyControlValueAccessor();
});

it('should reexport control properties', () => {
expect(form.control).toBe(formModel);
expect(form.value).toBe(formModel.value);
expect(form.valid).toBe(formModel.valid);
expect(form.invalid).toBe(formModel.invalid);
expect(form.pending).toBe(formModel.pending);
expect(form.errors).toBe(formModel.errors);
expect(form.pristine).toBe(formModel.pristine);
expect(form.dirty).toBe(formModel.dirty);
expect(form.touched).toBe(formModel.touched);
expect(form.untouched).toBe(formModel.untouched);
expect(form.statusChanges).toBe(formModel.statusChanges);
expect(form.valueChanges).toBe(formModel.valueChanges);
it('should extend AbstractControlDirective', () => {
expect(form instanceof AbstractControlDirective).toBe(true);
});

describe('addControl', () => {
Expand Down Expand Up @@ -312,21 +344,8 @@ export function main() {
loginControlDir.valueAccessor = new DummyControlValueAccessor();
});

it('should reexport control properties', () => {
expect(form.control).toBe(formModel);
expect(form.value).toBe(formModel.value);
expect(form.valid).toBe(formModel.valid);
expect(form.invalid).toBe(formModel.invalid);
expect(form.pending).toBe(formModel.pending);
expect(form.errors).toBe(formModel.errors);
expect(form.pristine).toBe(formModel.pristine);
expect(form.dirty).toBe(formModel.dirty);
expect(form.touched).toBe(formModel.touched);
expect(form.untouched).toBe(formModel.untouched);
expect(form.statusChanges).toBe(formModel.statusChanges);
expect(form.valueChanges).toBe(formModel.valueChanges);
expect(form.disabled).toBe(formModel.disabled);
expect(form.enabled).toBe(formModel.enabled);
it('should extend AbstractControlDirective', () => {
expect(form instanceof AbstractControlDirective).toBe(true);
});

describe('addControl & addFormGroup', () => {
Expand Down Expand Up @@ -390,21 +409,8 @@ export function main() {
controlGroupDir.name = 'group';
});

it('should reexport control properties', () => {
expect(controlGroupDir.control).toBe(formModel);
expect(controlGroupDir.value).toBe(formModel.value);
expect(controlGroupDir.valid).toBe(formModel.valid);
expect(controlGroupDir.invalid).toBe(formModel.invalid);
expect(controlGroupDir.pending).toBe(formModel.pending);
expect(controlGroupDir.errors).toBe(formModel.errors);
expect(controlGroupDir.pristine).toBe(formModel.pristine);
expect(controlGroupDir.dirty).toBe(formModel.dirty);
expect(controlGroupDir.touched).toBe(formModel.touched);
expect(controlGroupDir.untouched).toBe(formModel.untouched);
expect(controlGroupDir.statusChanges).toBe(formModel.statusChanges);
expect(controlGroupDir.valueChanges).toBe(formModel.valueChanges);
expect(controlGroupDir.disabled).toBe(formModel.disabled);
expect(controlGroupDir.enabled).toBe(formModel.enabled);
it('should extend AbstractControlDirective', () => {
expect(controlGroupDir instanceof AbstractControlDirective).toBe(true);
});
});

Expand All @@ -420,19 +426,8 @@ export function main() {
formArrayDir.name = 'array';
});

it('should reexport control properties', () => {
expect(formArrayDir.control).toBe(formModel);
expect(formArrayDir.value).toBe(formModel.value);
expect(formArrayDir.valid).toBe(formModel.valid);
expect(formArrayDir.invalid).toBe(formModel.invalid);
expect(formArrayDir.pending).toBe(formModel.pending);
expect(formArrayDir.errors).toBe(formModel.errors);
expect(formArrayDir.pristine).toBe(formModel.pristine);
expect(formArrayDir.dirty).toBe(formModel.dirty);
expect(formArrayDir.touched).toBe(formModel.touched);
expect(formArrayDir.untouched).toBe(formModel.untouched);
expect(formArrayDir.disabled).toBe(formModel.disabled);
expect(formArrayDir.enabled).toBe(formModel.enabled);
it('should extend AbstractControlDirective', () => {
expect(formArrayDir instanceof AbstractControlDirective).toBe(true);
});
});

Expand Down Expand Up @@ -464,7 +459,9 @@ export function main() {
controlDir.form = control;
});

it('should reexport control properties', () => { checkProperties(control); });
it('should extend AbstractControlDirective', () => {
expect(controlDir instanceof AbstractControlDirective).toBe(true);
});

it('should reexport new control properties', () => {
var newControl = new FormControl(null);
Expand Down Expand Up @@ -493,22 +490,8 @@ export function main() {
ngModel.valueAccessor = new DummyControlValueAccessor();
});

it('should reexport control properties', () => {
var control = ngModel.control;
expect(ngModel.control).toBe(control);
expect(ngModel.value).toBe(control.value);
expect(ngModel.valid).toBe(control.valid);
expect(ngModel.invalid).toBe(control.invalid);
expect(ngModel.pending).toBe(control.pending);
expect(ngModel.errors).toBe(control.errors);
expect(ngModel.pristine).toBe(control.pristine);
expect(ngModel.dirty).toBe(control.dirty);
expect(ngModel.touched).toBe(control.touched);
expect(ngModel.untouched).toBe(control.untouched);
expect(ngModel.statusChanges).toBe(control.statusChanges);
expect(ngModel.valueChanges).toBe(control.valueChanges);
expect(ngModel.disabled).toBe(control.disabled);
expect(ngModel.enabled).toBe(control.enabled);
it('should extend AbstractControlDirective', () => {
expect(ngModel instanceof AbstractControlDirective).toBe(true);
});

it('should throw when no value accessor with named control', () => {
Expand Down Expand Up @@ -594,21 +577,8 @@ export function main() {
controlNameDir._control = formModel;
});

it('should reexport control properties', () => {
expect(controlNameDir.control).toBe(formModel);
expect(controlNameDir.value).toBe(formModel.value);
expect(controlNameDir.valid).toBe(formModel.valid);
expect(controlNameDir.invalid).toBe(formModel.invalid);
expect(controlNameDir.pending).toBe(formModel.pending);
expect(controlNameDir.errors).toBe(formModel.errors);
expect(controlNameDir.pristine).toBe(formModel.pristine);
expect(controlNameDir.dirty).toBe(formModel.dirty);
expect(controlNameDir.touched).toBe(formModel.touched);
expect(controlNameDir.untouched).toBe(formModel.untouched);
expect(controlNameDir.statusChanges).toBe(formModel.statusChanges);
expect(controlNameDir.valueChanges).toBe(formModel.valueChanges);
expect(controlNameDir.disabled).toBe(formModel.disabled);
expect(controlNameDir.enabled).toBe(formModel.enabled);
it('should extend AbstractControlDirective', () => {
expect(controlNameDir instanceof AbstractControlDirective).toBe(true);
});
});
});
Expand Down
2 changes: 2 additions & 0 deletions tools/public_api_guard/forms/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export declare abstract class AbstractControlDirective {
valid: boolean;
value: any;
valueChanges: Observable<any>;
getError(errorCode: string, path?: string[]): any;
hasError(errorCode: string, path?: string[]): boolean;
reset(value?: any): void;
}

Expand Down

0 comments on commit dfa66b7

Please sign in to comment.