Skip to content

Commit 2141c4e

Browse files
committed
feat(input): Add custom error state function
1 parent 0e24345 commit 2141c4e

File tree

4 files changed

+82
-6
lines changed

4 files changed

+82
-6
lines changed

src/demo-app/input/input-demo.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ <h4>Inside a form</h4>
9494

9595
<button color="primary" md-raised-button>Submit</button>
9696
</form>
97+
98+
<h4>With a custom error function</h4>
99+
<md-input-container [errorStateFn]="customErrorStateFn">
100+
<input mdInput placeholder="example" [(ngModel)]="errorMessageExample4" required>
101+
<md-error>This field is required</md-error>
102+
</md-input-container>
103+
97104
</md-card-content>
98105
</md-card>
99106

src/demo-app/input/input-demo.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Component} from '@angular/core';
2-
import {FormControl, Validators} from '@angular/forms';
2+
import {FormControl, Validators, NgControl} from '@angular/forms';
33

44

55
let max = 5;
@@ -23,6 +23,7 @@ export class InputDemo {
2323
errorMessageExample1: string;
2424
errorMessageExample2: string;
2525
errorMessageExample3: string;
26+
errorMessageExample4: string;
2627
items: any[] = [
2728
{ value: 10 },
2829
{ value: 20 },
@@ -40,4 +41,11 @@ export class InputDemo {
4041
this.items.push({ value: ++max });
4142
}
4243
}
44+
45+
customErrorStateFn(c: NgControl): boolean {
46+
const isDirty = c.dirty;
47+
const isInvalid = c.invalid;
48+
49+
return isDirty && isInvalid;
50+
}
4351
}

src/lib/input/input-container.spec.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
FormGroupDirective,
77
FormsModule,
88
NgForm,
9+
NgControl,
910
ReactiveFormsModule,
1011
Validators
1112
} from '@angular/forms';
@@ -56,6 +57,7 @@ describe('MdInputContainer', function () {
5657
MdInputContainerWithDynamicPlaceholder,
5758
MdInputContainerWithFormControl,
5859
MdInputContainerWithFormErrorMessages,
60+
MdInputContainerWithCustomErrorStateFunction,
5961
MdInputContainerWithFormGroupErrorMessages,
6062
MdInputContainerWithId,
6163
MdInputContainerWithPrefixAndSuffix,
@@ -669,6 +671,36 @@ describe('MdInputContainer', function () {
669671
});
670672
}));
671673

674+
it('should display an error message when a custom error function returns true', async(() => {
675+
fixture.destroy();
676+
677+
let customFixture = TestBed.createComponent(MdInputContainerWithCustomErrorStateFunction);
678+
let component: MdInputContainerWithCustomErrorStateFunction;
679+
680+
customFixture.detectChanges();
681+
component = customFixture.componentInstance;
682+
containerEl = customFixture.debugElement.query(By.css('md-input-container')).nativeElement;
683+
684+
expect(component.formControl.invalid).toBe(true, 'Expected form control to be invalid');
685+
expect(containerEl.querySelectorAll('md-error').length).toBe(0, 'Expected no error messages');
686+
687+
component.formControl.markAsTouched();
688+
customFixture.detectChanges();
689+
690+
customFixture.whenStable().then(() => {
691+
expect(containerEl.querySelectorAll('md-error').length)
692+
.toBe(0, 'Expected no error messages after being touched.');
693+
694+
component.errorState = true;
695+
customFixture.detectChanges();
696+
697+
customFixture.whenStable().then(() => {
698+
expect(containerEl.querySelectorAll('md-error').length)
699+
.toBe(1, 'Expected one error messages to have been rendered.');
700+
});
701+
});
702+
}));
703+
672704
it('should hide the errors and show the hints once the input becomes valid', async(() => {
673705
testComponent.formControl.markAsTouched();
674706
fixture.detectChanges();
@@ -982,6 +1014,27 @@ class MdInputContainerWithFormErrorMessages {
9821014
renderError = true;
9831015
}
9841016

1017+
@Component({
1018+
template: `
1019+
<form #form="ngForm" novalidate>
1020+
<md-input-container [errorStateFn]="customErrorStateFn.bind(this)">
1021+
<input mdInput [formControl]="formControl">
1022+
<md-hint>Please type something</md-hint>
1023+
<md-error>This field is required</md-error>
1024+
</md-input-container>
1025+
</form>
1026+
`
1027+
})
1028+
class MdInputContainerWithCustomErrorStateFunction {
1029+
@ViewChild('form') form: NgForm;
1030+
formControl = new FormControl('', Validators.required);
1031+
errorState = false;
1032+
1033+
customErrorStateFn(c: NgControl): boolean {
1034+
return this.errorState;
1035+
}
1036+
}
1037+
9851038
@Component({
9861039
template: `
9871040
<form [formGroup]="formGroup" novalidate>

src/lib/input/input-container.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,9 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterC
337337
}
338338
private _floatPlaceholder: FloatPlaceholderType = 'auto';
339339

340+
/** A function used to control when error messages are shown. */
341+
@Input() errorStateFn: (control: NgControl) => boolean;
342+
340343
/** Reference to the input's underline element. */
341344
@ViewChild('underline') underlineRef: ElementRef;
342345

@@ -393,6 +396,16 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterC
393396
/** Whether the input container is in an error state. */
394397
_isErrorState(): boolean {
395398
const control = this._mdInputChild._ngControl;
399+
return this.errorStateFn ? this.errorStateFn(control) : this._defaultErrorStateFn(control);
400+
}
401+
402+
/** Determines whether to display hints or errors. */
403+
_getDisplayedMessages(): 'error' | 'hint' {
404+
return (this._errorChildren.length > 0 && this._isErrorState()) ? 'error' : 'hint';
405+
}
406+
407+
/** Default error state calculation */
408+
private _defaultErrorStateFn(control: NgControl): boolean {
396409
const isInvalid = control && control.invalid;
397410
const isTouched = control && control.touched;
398411
const isSubmitted = (this._parentFormGroup && this._parentFormGroup.submitted) ||
@@ -401,11 +414,6 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterC
401414
return !!(isInvalid && (isTouched || isSubmitted));
402415
}
403416

404-
/** Determines whether to display hints or errors. */
405-
_getDisplayedMessages(): 'error' | 'hint' {
406-
return (this._errorChildren.length > 0 && this._isErrorState()) ? 'error' : 'hint';
407-
}
408-
409417
/**
410418
* Ensure that there is only one placeholder (either `input` attribute or child element with the
411419
* `md-placeholder` attribute.

0 commit comments

Comments
 (0)