Skip to content

Commit 42dab5e

Browse files
committed
feat(input): add directive for displayed error messages
Adds the `md-error` directive that can be utilised to display validation errors to the user. Example: ``` <md-input-container> <input mdInput placeholder="email" [formControl]="emailFormControl"> <md-error *ngIf="emailFormControl.errors.required">This field is required</md-error> <md-error *ngIf="emailFormControl.errors.pattern"> Please enter a valid email address </md-error> </md-input-container> ``` The `md-input-container` behavior is as follows: * If there is an error and the user interacted with the input, the errors will be shown. * If there is an error and the user submitted the a form that wraps the input, the errors will be shown. * If there are errors to be shown on an input container that has `md-hint`-s, the hints will be hidden. * If an input with hints and errors becomes valid, the hint won't be displayed anymore. **Note:** At the moment, all hints will be hidden when an error is shown. This might not be intended for some cases (e.g. with a character counter). It might make sense to only hide the one in the `start` slot, but this could be addressed separately.
1 parent e964734 commit 42dab5e

File tree

10 files changed

+347
-32
lines changed

10 files changed

+347
-32
lines changed

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,49 @@
5151
</md-card-content>
5252
</md-card>
5353

54+
<md-card class="demo-card demo-basic">
55+
<md-toolbar color="primary">Error messages</md-toolbar>
56+
<md-card-content>
57+
<h4>Regular</h4>
58+
59+
<p>
60+
<md-input-container>
61+
<input mdInput placeholder="example" [(ngModel)]="errorMessageExample1" required>
62+
<md-error>This field is required</md-error>
63+
</md-input-container>
64+
65+
<md-input-container>
66+
<input mdInput placeholder="email" [formControl]="emailFormControl">
67+
<md-error *ngIf="emailFormControl.errors.required">This field is required</md-error>
68+
<md-error *ngIf="emailFormControl.errors.pattern">
69+
Please enter a valid email address
70+
</md-error>
71+
</md-input-container>
72+
</p>
73+
74+
<h4>With hint</h4>
75+
76+
<md-input-container>
77+
<input mdInput placeholder="example" [(ngModel)]="errorMessageExample2" required>
78+
<md-error>This field is required</md-error>
79+
<md-hint>Please type something here</md-hint>
80+
</md-input-container>
81+
82+
83+
<form novalidate>
84+
<h4>Inside a form</h4>
85+
86+
<md-input-container>
87+
<input mdInput name="example" placeholder="example"
88+
[(ngModel)]="errorMessageExample3" required>
89+
<md-error>This field is required</md-error>
90+
</md-input-container>
91+
92+
<button color="primary" md-raised-button>Submit</button>
93+
</form>
94+
</md-card-content>
95+
</md-card>
96+
5497
<md-card class="demo-card demo-basic">
5598
<md-toolbar color="primary">Prefix + Suffix</md-toolbar>
5699
<md-card-content>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {FormControl, Validators} from '@angular/forms';
44

55
let max = 5;
66

7+
const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
8+
79
@Component({
810
moduleId: module.id,
911
selector: 'input-demo',
@@ -17,6 +19,9 @@ export class InputDemo {
1719
ctrlDisabled = false;
1820

1921
name: string;
22+
errorMessageExample1: string;
23+
errorMessageExample2: string;
24+
errorMessageExample3: string;
2025
items: any[] = [
2126
{ value: 10 },
2227
{ value: 20 },
@@ -26,6 +31,7 @@ export class InputDemo {
2631
];
2732
rows = 8;
2833
formControl = new FormControl('hello', Validators.required);
34+
emailFormControl = new FormControl('', [Validators.required, Validators.pattern(EMAIL_REGEX)]);
2935
model = 'hello';
3036

3137
addABunch(n: number) {

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {TestBed, async, fakeAsync, tick, ComponentFixture} from '@angular/core/testing';
22
import {Component, OnDestroy, QueryList, ViewChild, ViewChildren} from '@angular/core';
33
import {By} from '@angular/platform-browser';
4+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
45
import {MdAutocompleteModule, MdAutocompleteTrigger} from './index';
56
import {OverlayContainer} from '../core/overlay/overlay-container';
67
import {MdInputModule} from '../input/index';
@@ -27,7 +28,11 @@ describe('MdAutocomplete', () => {
2728
dir = 'ltr';
2829
TestBed.configureTestingModule({
2930
imports: [
30-
MdAutocompleteModule.forRoot(), MdInputModule.forRoot(), FormsModule, ReactiveFormsModule
31+
MdAutocompleteModule.forRoot(),
32+
MdInputModule.forRoot(),
33+
FormsModule,
34+
ReactiveFormsModule,
35+
NoopAnimationsModule
3136
],
3237
declarations: [
3338
SimpleAutocomplete,

src/lib/input/_input-theme.scss

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
$warn: map-get($theme, warn);
99
$background: map-get($theme, background);
1010
$foreground: map-get($theme, foreground);
11-
11+
1212
// Placeholder colors. Required is used for the `*` star shown in the placeholder.
1313
$input-placeholder-color: mat-color($foreground, hint-text);
1414
$input-floating-placeholder-color: mat-color($primary);
1515
$input-required-placeholder-color: mat-color($accent);
16-
16+
1717
// Underline colors.
1818
$input-underline-color: mat-color($foreground, divider);
1919
$input-underline-color-accent: mat-color($accent);
@@ -64,7 +64,7 @@
6464
}
6565
}
6666

67-
.mat-input-container.ng-invalid.ng-touched:not(.mat-focused) {
67+
.mat-input-invalid {
6868
.mat-input-placeholder,
6969
.mat-placeholder-required {
7070
color: $input-underline-color-warn;
@@ -73,5 +73,13 @@
7373
.mat-input-underline {
7474
border-color: $input-underline-color-warn;
7575
}
76+
77+
.mat-input-ripple {
78+
background-color: $input-underline-color-warn;
79+
}
80+
}
81+
82+
.mat-input-error {
83+
color: $input-underline-color-warn;
7684
}
7785
}

src/lib/input/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {NgModule, ModuleWithProviders} from '@angular/core';
2-
import {MdPlaceholder, MdInputContainer, MdHint, MdInputDirective} from './input-container';
2+
import {MdPlaceholder, MdInputContainer, MdHint, MdInputDirective, MdErrorDirective} from './input-container';
33
import {MdTextareaAutosize} from './autosize';
44
import {CommonModule} from '@angular/common';
55
import {FormsModule} from '@angular/forms';
@@ -12,7 +12,8 @@ import {PlatformModule} from '../core/platform/index';
1212
MdInputContainer,
1313
MdHint,
1414
MdTextareaAutosize,
15-
MdInputDirective
15+
MdInputDirective,
16+
MdErrorDirective
1617
],
1718
imports: [
1819
CommonModule,
@@ -24,7 +25,8 @@ import {PlatformModule} from '../core/platform/index';
2425
MdInputContainer,
2526
MdHint,
2627
MdTextareaAutosize,
27-
MdInputDirective
28+
MdInputDirective,
29+
MdErrorDirective
2830
],
2931
})
3032
export class MdInputModule {

src/lib/input/input-container.html

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@
3636
[class.mat-warn]="dividerColor == 'warn'"></span>
3737
</div>
3838

39-
<div *ngIf="hintLabel != ''" [attr.id]="_hintLabelId" class="mat-hint">{{hintLabel}}</div>
40-
<ng-content select="md-hint, mat-hint"></ng-content>
39+
<div class="mat-input-hint-wrapper" [ngSwitch]="_getDisplayedMessages()">
40+
<div *ngSwitchCase="'error'" [@transitionMessages]="_messageAnimationState">
41+
<ng-content select="md-error, mat-error"></ng-content>
42+
</div>
43+
44+
<div *ngSwitchCase="'hint'" class="mat-input-hint-clearfix" [@transitionMessages]="_messageAnimationState">
45+
<div *ngIf="hintLabel != ''" [attr.id]="_hintLabelId" class="mat-hint">{{hintLabel}}</div>
46+
<ng-content select="md-hint, mat-hint"></ng-content>
47+
</div>
48+
</div>
4149
</div>

src/lib/input/input-container.scss

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55

66
$mat-input-floating-placeholder-scale-factor: 0.75 !default;
7+
$mat-input-wrapper-spacing: 1em !default;
78

89
// Gradient for showing the dashed line when the input is disabled.
910
$mat-input-underline-disabled-background-image:
@@ -41,7 +42,7 @@ $mat-input-underline-disabled-background-image:
4142
// Global wrapper. We need to apply margin to the element for spacing, but
4243
// cannot apply it to the host element directly.
4344
.mat-input-wrapper {
44-
margin: 1em 0;
45+
margin: $mat-input-wrapper-spacing 0;
4546
// Account for the underline which has 4px of margin + 2px of border.
4647
padding-bottom: 6px;
4748
}
@@ -219,29 +220,53 @@ $mat-input-underline-disabled-background-image:
219220
}
220221
}
221222

222-
// The hint is shown below the underline. There can be more than one; one at the start
223-
// and one at the end.
224-
.mat-hint {
225-
display: block;
223+
// Wrapper for the hints and error messages. Provides positioning and text size.
224+
// Note that we're using `top` in order to allow for stacked children to flow downwards.
225+
.mat-input-hint-wrapper {
226226
position: absolute;
227227
font-size: 75%;
228-
bottom: 0;
228+
top: 100%;
229+
width: 100%;
230+
margin-top: -$mat-input-wrapper-spacing;
231+
}
232+
233+
// The hint is shown below the underline. There can be
234+
// more than one; one at the start and one at the end.
235+
.mat-hint {
236+
display: block;
237+
float: left;
229238

230239
&.mat-right {
231-
right: 0;
240+
float: right;
232241
}
233242

234243
[dir='rtl'] & {
235-
right: 0;
236-
left: auto;
244+
float: right;
237245

238246
&.mat-right {
239-
right: auto;
240-
left: 0;
247+
float: left;
241248
}
242249
}
243250
}
244251

252+
// Clears the floats on the hints. Necessary for the `transform` animation to work.
253+
.mat-input-hint-clearfix {
254+
&::before,
255+
&::after {
256+
content: '';
257+
display: table;
258+
}
259+
260+
&::after {
261+
clear: both;
262+
}
263+
}
264+
265+
// Single errror message displayed beneath the input.
266+
.mat-input-error {
267+
display: block;
268+
}
269+
245270
.mat-input-prefix, .mat-input-suffix {
246271
// Prevents the prefix and suffix from stretching together with the container.
247272
width: 0.1px;

0 commit comments

Comments
 (0)