diff --git a/modules/angular2/src/forms/directives/checkbox_value_accessor.ts b/modules/angular2/src/forms/directives/checkbox_value_accessor.ts index dfd020198e..9f7f79a0f7 100644 --- a/modules/angular2/src/forms/directives/checkbox_value_accessor.ts +++ b/modules/angular2/src/forms/directives/checkbox_value_accessor.ts @@ -17,15 +17,17 @@ import {ControlValueAccessor} from './control_value_accessor'; @Directive({ selector: 'input[type=checkbox][ng-control],input[type=checkbox][ng-form-control],input[type=checkbox][ng-model]', - hostListeners: {'change': 'onChange($event.target.checked)'}, + hostListeners: {'change': 'onChange($event.target.checked)', 'blur': 'onTouched()'}, hostProperties: {'checked': 'checked'} }) export class CheckboxControlValueAccessor implements ControlValueAccessor { checked: boolean; onChange: Function; + onTouched: Function; constructor(cd: ControlDirective, private _elementRef: ElementRef, private _renderer: Renderer) { this.onChange = (_) => {}; + this.onTouched = (_) => {}; cd.valueAccessor = this; } @@ -34,5 +36,6 @@ export class CheckboxControlValueAccessor implements ControlValueAccessor { this._elementRef.boundElementIndex, 'checked', value) } - registerOnChange(fn) { this.onChange = fn; } + registerOnChange(fn): void { this.onChange = fn; } + registerOnTouched(fn): void { this.onTouched = fn; } } \ No newline at end of file diff --git a/modules/angular2/src/forms/directives/control_value_accessor.ts b/modules/angular2/src/forms/directives/control_value_accessor.ts index aa31289200..c28cdec221 100644 --- a/modules/angular2/src/forms/directives/control_value_accessor.ts +++ b/modules/angular2/src/forms/directives/control_value_accessor.ts @@ -1,4 +1,5 @@ export interface ControlValueAccessor { writeValue(obj: any): void; - registerOnChange(fun: any): void; + registerOnChange(fn: any): void; + registerOnTouched(fn: any): void; } \ No newline at end of file diff --git a/modules/angular2/src/forms/directives/default_value_accessor.ts b/modules/angular2/src/forms/directives/default_value_accessor.ts index 0922b069df..5b5bc7280d 100644 --- a/modules/angular2/src/forms/directives/default_value_accessor.ts +++ b/modules/angular2/src/forms/directives/default_value_accessor.ts @@ -19,16 +19,21 @@ import {ControlValueAccessor} from './control_value_accessor'; @Directive({ selector: 'input:not([type=checkbox])[ng-control],textarea[ng-control],input:not([type=checkbox])[ng-form-control],textarea[ng-form-control],input:not([type=checkbox])[ng-model],textarea[ng-model]', - hostListeners: - {'change': 'onChange($event.target.value)', 'input': 'onChange($event.target.value)'}, + hostListeners: { + 'change': 'onChange($event.target.value)', + 'input': 'onChange($event.target.value)', + 'blur': 'onTouched()' + }, hostProperties: {'value': 'value'} }) export class DefaultValueAccessor implements ControlValueAccessor { value = null; onChange: Function; + onTouched: Function; constructor(cd: ControlDirective, private _elementRef: ElementRef, private _renderer: Renderer) { this.onChange = (_) => {}; + this.onTouched = (_) => {}; cd.valueAccessor = this; } @@ -37,5 +42,6 @@ export class DefaultValueAccessor implements ControlValueAccessor { this._elementRef.boundElementIndex, 'value', value) } - registerOnChange(fn) { this.onChange = fn; } -} + registerOnChange(fn): void { this.onChange = fn; } + registerOnTouched(fn): void { this.onTouched = fn; } +} \ No newline at end of file diff --git a/modules/angular2/src/forms/directives/select_control_value_accessor.ts b/modules/angular2/src/forms/directives/select_control_value_accessor.ts index 5a4889226e..35242e9df9 100644 --- a/modules/angular2/src/forms/directives/select_control_value_accessor.ts +++ b/modules/angular2/src/forms/directives/select_control_value_accessor.ts @@ -18,16 +18,21 @@ import {ControlValueAccessor} from './control_value_accessor'; */ @Directive({ selector: 'select[ng-control],select[ng-form-control],select[ng-model]', - hostListeners: - {'change': 'onChange($event.target.value)', 'input': 'onChange($event.target.value)'}, + hostListeners: { + 'change': 'onChange($event.target.value)', + 'input': 'onChange($event.target.value)', + 'blur': 'onTouched()' + }, hostProperties: {'value': 'value'} }) export class SelectControlValueAccessor implements ControlValueAccessor { value = null; onChange: Function; + onTouched: Function; constructor(cd: ControlDirective, private _elementRef: ElementRef, private _renderer: Renderer) { this.onChange = (_) => {}; + this.onTouched = (_) => {}; this.value = ''; cd.valueAccessor = this; } @@ -37,5 +42,6 @@ export class SelectControlValueAccessor implements ControlValueAccessor { this._elementRef.boundElementIndex, 'value', value) } - registerOnChange(fn) { this.onChange = fn; } -} + registerOnChange(fn): void { this.onChange = fn; } + registerOnTouched(fn): void { this.onTouched = fn; } +} \ No newline at end of file diff --git a/modules/angular2/src/forms/directives/shared.ts b/modules/angular2/src/forms/directives/shared.ts index 7b24b8ce11..2b4cf5401b 100644 --- a/modules/angular2/src/forms/directives/shared.ts +++ b/modules/angular2/src/forms/directives/shared.ts @@ -27,6 +27,9 @@ export function setUpControl(c: Control, dir: ControlDirective) { // model -> view c.registerOnChange(newValue => dir.valueAccessor.writeValue(newValue)); + + // touched + dir.valueAccessor.registerOnTouched(() => c.touch()); } function _throwError(dir: ControlDirective, message: string): void { diff --git a/modules/angular2/src/forms/model.ts b/modules/angular2/src/forms/model.ts index 88cbd87506..f375c29a36 100644 --- a/modules/angular2/src/forms/model.ts +++ b/modules/angular2/src/forms/model.ts @@ -30,6 +30,7 @@ export class AbstractControl { _status: string; _errors: StringMap; _pristine: boolean; + _touched: boolean; _parent: any; /* ControlGroup | ControlArray */ validator: Function; @@ -38,6 +39,7 @@ export class AbstractControl { constructor(validator: Function) { this.validator = validator; this._pristine = true; + this._touched = false; } get value(): any { return this._value; } @@ -52,8 +54,14 @@ export class AbstractControl { get dirty(): boolean { return !this.pristine; } + get touched(): boolean { return this._touched; } + + get untouched(): boolean { return !this._touched; } + get valueChanges(): Observable { return this._valueChanges; } + touch(): void { this._touched = true; } + setParent(parent) { this._parent = parent; } updateValidity({onlySelf}: {onlySelf?: boolean} = {}): void { diff --git a/modules/angular2/test/forms/directives_spec.ts b/modules/angular2/test/forms/directives_spec.ts index 98b77aa1e7..d1422a665b 100644 --- a/modules/angular2/test/forms/directives_spec.ts +++ b/modules/angular2/test/forms/directives_spec.ts @@ -29,6 +29,7 @@ class DummyControlValueAccessor implements ControlValueAccessor { writtenValue; registerOnChange(fn) {} + registerOnTouched(fn) {} writeValue(obj: any): void { this.writtenValue = obj; } } diff --git a/modules/angular2/test/forms/integration_spec.ts b/modules/angular2/test/forms/integration_spec.ts index 44f4c02297..82290406a1 100644 --- a/modules/angular2/test/forms/integration_spec.ts +++ b/modules/angular2/test/forms/integration_spec.ts @@ -138,6 +138,32 @@ export function main() { }); })); + it("should mark controls as touched after interacting with the DOM control", + inject([TestBed, AsyncTestCompleter], (tb, async) => { + var login = new Control("oldValue"); + var form = new ControlGroup({"login": login}); + var ctx = MyComp.create({form: form}); + + var t = `
+ +
`; + + tb.createView(MyComp, {context: ctx, html: t}) + .then((view) => { + view.detectChanges(); + + var loginEl = view.querySelector("input"); + + expect(login.touched).toBe(false); + + dispatchEvent(loginEl, "blur"); + + expect(login.touched).toBe(true); + + async.done(); + }); + })); + describe("different control types", () => { it("should support ", inject([TestBed, AsyncTestCompleter], (tb, async) => { var ctx = MyComp.create({form: new ControlGroup({"text": new Control("old")})}); @@ -590,6 +616,7 @@ class WrappedValue implements ControlValueAccessor { writeValue(value) { this.value = `!${value}!`; } registerOnChange(fn) { this.onChange = fn; } + registerOnTouched(fn) {} handleOnChange(value) { this.onChange(value.substring(1, value.length - 1)); } }