From 84841d042c6c805c25bdc4fe51c4389740b081ec Mon Sep 17 00:00:00 2001 From: Will Howell Date: Thu, 22 Jun 2017 11:55:43 -0400 Subject: [PATCH] Address comments --- src/lib/core/core.ts | 2 +- src/lib/core/error/error-options.ts | 39 ++++++++++++++++++++++++--- src/lib/input/input-container.spec.ts | 20 ++++++-------- src/lib/input/input-container.ts | 27 +++++-------------- src/lib/input/input.md | 13 +++++++-- 5 files changed, 61 insertions(+), 40 deletions(-) diff --git a/src/lib/core/core.ts b/src/lib/core/core.ts index b7bd62339038..7088c3817186 100644 --- a/src/lib/core/core.ts +++ b/src/lib/core/core.ts @@ -120,7 +120,7 @@ export { // Error export { - ErrorStateMatcherType, + ErrorStateMatcher, ErrorOptions, MD_ERROR_GLOBAL_OPTIONS } from './error/error-options'; diff --git a/src/lib/core/error/error-options.ts b/src/lib/core/error/error-options.ts index 223575629f6c..d2f71b1b719a 100644 --- a/src/lib/core/error/error-options.ts +++ b/src/lib/core/error/error-options.ts @@ -1,14 +1,45 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + import {InjectionToken} from '@angular/core'; import {NgControl, FormGroupDirective, NgForm} from '@angular/forms'; /** Injection token that can be used to specify the global error options. */ export const MD_ERROR_GLOBAL_OPTIONS = - new InjectionToken<() => boolean>('md-error-global-options'); + new InjectionToken('md-error-global-options'); -export type ErrorStateMatcherType = +export type ErrorStateMatcher = (control: NgControl, parentFormGroup: FormGroupDirective, parentForm: NgForm) => boolean; export interface ErrorOptions { - errorStateMatcher?: ErrorStateMatcherType; - showOnDirty?: boolean; + errorStateMatcher?: ErrorStateMatcher; +} + +export class DefaultErrorStateMatcher { + + errorStateMatcher(control: NgControl, formGroup: FormGroupDirective, form: NgForm): boolean { + const isInvalid = control && control.invalid; + const isTouched = control && control.touched; + const isSubmitted = (formGroup && formGroup.submitted) || + (form && form.submitted); + + return !!(isInvalid && (isTouched || isSubmitted)); + } +} + +export class ShowOnDirtyErrorStateMatcher { + + errorStateMatcher(control: NgControl, formGroup: FormGroupDirective, form: NgForm): boolean { + const isInvalid = control && control.invalid; + const isDirty = control && control.dirty; + const isSubmitted = (formGroup && formGroup.submitted) || + (form && form.submitted); + + return !!(isInvalid && (isDirty || isSubmitted)); + } } diff --git a/src/lib/input/input-container.spec.ts b/src/lib/input/input-container.spec.ts index 0fda4f7d59a2..9e8590df8ac3 100644 --- a/src/lib/input/input-container.spec.ts +++ b/src/lib/input/input-container.spec.ts @@ -24,7 +24,7 @@ import { getMdInputContainerPlaceholderConflictError } from './input-container-errors'; import {MD_PLACEHOLDER_GLOBAL_OPTIONS} from '../core/placeholder/placeholder-options'; -import {MD_ERROR_GLOBAL_OPTIONS} from '../core/error/error-options'; +import {MD_ERROR_GLOBAL_OPTIONS, ShowOnDirtyErrorStateMatcher} from '../core/error/error-options'; describe('MdInputContainer', function () { beforeEach(async(() => { @@ -676,8 +676,6 @@ describe('MdInputContainer', function () { })); it('should display an error message when the parent form group is submitted', async(() => { - fixture.destroy(); - let groupFixture = TestBed.createComponent(MdInputContainerWithFormGroupErrorMessages); let component: MdInputContainerWithFormGroupErrorMessages; @@ -709,8 +707,6 @@ describe('MdInputContainer', function () { })); it('should display an error message when a custom error matcher returns true', async(() => { - fixture.destroy(); - let customFixture = TestBed.createComponent(MdInputContainerWithCustomErrorStateMatcher); let component: MdInputContainerWithCustomErrorStateMatcher; @@ -726,14 +722,14 @@ describe('MdInputContainer', function () { customFixture.whenStable().then(() => { expect(containerEl.querySelectorAll('md-error').length) - .toBe(0, 'Expected no error messages after being touched.'); + .toBe(0, 'Expected no error messages after being touched.'); component.errorState = true; customFixture.detectChanges(); customFixture.whenStable().then(() => { expect(containerEl.querySelectorAll('md-error').length) - .toBe(1, 'Expected one error messages to have been rendered.'); + .toBe(1, 'Expected one error messages to have been rendered.'); }); }); })); @@ -774,7 +770,7 @@ describe('MdInputContainer', function () { expect(containerEl.querySelectorAll('md-error').length).toBe(1, 'Expected an error message'); }); - it('should display an error message when global setting shows errors on dirty', async() => { + it('should display an error message when using ShowOnDirtyErrorStateMatcher', async(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ imports: [ @@ -787,7 +783,7 @@ describe('MdInputContainer', function () { MdInputContainerWithFormErrorMessages ], providers: [ - { provide: MD_ERROR_GLOBAL_OPTIONS, useValue: { showOnDirty: true } } + { provide: MD_ERROR_GLOBAL_OPTIONS, useClass: ShowOnDirtyErrorStateMatcher } ] }); @@ -805,18 +801,18 @@ describe('MdInputContainer', function () { customFixture.whenStable().then(() => { expect(containerEl.querySelectorAll('md-error').length) - .toBe(0, 'Expected no error messages when touched'); + .toBe(0, 'Expected no error messages when touched'); testComponent.formControl.markAsDirty(); customFixture.detectChanges(); customFixture.whenStable().then(() => { expect(containerEl.querySelectorAll('md-error').length) - .toBe(1, 'Expected one error message when dirty'); + .toBe(1, 'Expected one error message when dirty'); }); }); - }); + })); it('should hide the errors and show the hints once the input becomes valid', async(() => { testComponent.formControl.markAsTouched(); diff --git a/src/lib/input/input-container.ts b/src/lib/input/input-container.ts index 3770d2da5870..c9db2cf8da1a 100644 --- a/src/lib/input/input-container.ts +++ b/src/lib/input/input-container.ts @@ -43,7 +43,8 @@ import { MD_PLACEHOLDER_GLOBAL_OPTIONS } from '../core/placeholder/placeholder-options'; import { - ErrorStateMatcherType, + DefaultErrorStateMatcher, + ErrorStateMatcher, ErrorOptions, MD_ERROR_GLOBAL_OPTIONS } from '../core/error/error-options'; @@ -196,7 +197,7 @@ export class MdInputDirective { } /** A function used to control when error messages are shown. */ - @Input() errorStateMatcher: ErrorStateMatcherType; + @Input() errorStateMatcher: ErrorStateMatcher; /** The input element's value. */ get value() { return this._elementRef.nativeElement.value; } @@ -240,7 +241,8 @@ export class MdInputDirective { this.id = this.id; this._errorOptions = errorOptions ? errorOptions : {}; - this.errorStateMatcher = this._errorOptions.errorStateMatcher || undefined; + this.errorStateMatcher = this._errorOptions.errorStateMatcher + || new DefaultErrorStateMatcher().errorStateMatcher; } /** Focuses the input element. */ @@ -263,24 +265,7 @@ export class MdInputDirective { /** Whether the input is in an error state. */ _isErrorState(): boolean { const control = this._ngControl; - return this.errorStateMatcher - ? this.errorStateMatcher(control, this._parentFormGroup, this._parentForm) - : this._defaultErrorStateMatcher(control); - } - - /** Default error state calculation */ - private _defaultErrorStateMatcher(control: NgControl): boolean { - const isInvalid = control && control.invalid; - const isTouched = control && control.touched; - const isDirty = control && control.dirty; - const isSubmitted = (this._parentFormGroup && this._parentFormGroup.submitted) || - (this._parentForm && this._parentForm.submitted); - - if (this._errorOptions.showOnDirty) { - return !!(isInvalid && (isDirty || isSubmitted)); - } - - return !!(isInvalid && (isTouched || isSubmitted)); + return this.errorStateMatcher(control, this._parentFormGroup, this._parentForm); } /** Make sure the input is a supported type. */ diff --git a/src/lib/input/input.md b/src/lib/input/input.md index 6043f9abaedc..e8000f311eb1 100644 --- a/src/lib/input/input.md +++ b/src/lib/input/input.md @@ -142,8 +142,17 @@ to all inputs. Here are the available global options: - | Name | Type | Description | | ----------------- | -------- | ----------- | | errorStateMatcher | Function | Returns a boolean specifying if the error should be shown | -| showOnDirty | boolean | If true, the error will show when the control is dirty, not touched. |P \ No newline at end of file + + +If you just wish to make all inputs behave the same as the default, but show errors when +dirty instead of touched, you can use the `ShowOnDirtyErrorStateMatcher` implementation. + +```ts +@NgModule({ + providers: [ + { provide: MD_ERROR_GLOBAL_OPTIONS, useClass: ShowOnDirtyErrorStateMatcher } + ] +}) \ No newline at end of file