Skip to content

Commit

Permalink
Address comments
Browse files Browse the repository at this point in the history
  • Loading branch information
willshowell committed Jun 22, 2017
1 parent 3ff8d87 commit 84841d0
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 40 deletions.
2 changes: 1 addition & 1 deletion src/lib/core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export {

// Error
export {
ErrorStateMatcherType,
ErrorStateMatcher,
ErrorOptions,
MD_ERROR_GLOBAL_OPTIONS
} from './error/error-options';
Expand Down
39 changes: 35 additions & 4 deletions src/lib/core/error/error-options.ts
Original file line number Diff line number Diff line change
@@ -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<ErrorOptions>('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));
}
}
20 changes: 8 additions & 12 deletions src/lib/input/input-container.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -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.');
});
});
}));
Expand Down Expand Up @@ -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: [
Expand All @@ -787,7 +783,7 @@ describe('MdInputContainer', function () {
MdInputContainerWithFormErrorMessages
],
providers: [
{ provide: MD_ERROR_GLOBAL_OPTIONS, useValue: { showOnDirty: true } }
{ provide: MD_ERROR_GLOBAL_OPTIONS, useClass: ShowOnDirtyErrorStateMatcher }
]
});

Expand All @@ -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();
Expand Down
27 changes: 6 additions & 21 deletions src/lib/input/input-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -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. */
Expand All @@ -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. */
Expand Down
13 changes: 11 additions & 2 deletions src/lib/input/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -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


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 }
]
})

0 comments on commit 84841d0

Please sign in to comment.