Skip to content

Commit c70b460

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 d78a370 commit c70b460

File tree

9 files changed

+334
-28
lines changed

9 files changed

+334
-28
lines changed

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,49 @@
4848
</md-card-content>
4949
</md-card>
5050

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

44

55
$mat-input-floating-placeholder-scale-factor: 0.75 !default;
6+
$mat-input-wrapper-spacing: 1em !default;
67

78
// Gradient for showing the dashed line when the input is disabled.
89
$mat-input-underline-disabled-background-image:
@@ -32,7 +33,7 @@ $mat-input-underline-disabled-background-image:
3233
// Global wrapper. We need to apply margin to the element for spacing, but
3334
// cannot apply it to the host element directly.
3435
.mat-input-wrapper {
35-
margin: 1em 0;
36+
margin: $mat-input-wrapper-spacing 0;
3637
// Account for the underline which has 4px of margin + 2px of border.
3738
padding-bottom: 6px;
3839
}
@@ -211,29 +212,53 @@ $mat-input-underline-disabled-background-image:
211212
}
212213
}
213214

214-
// The hint is shown below the underline. There can be more than one; one at the start
215-
// and one at the end.
216-
.mat-hint {
217-
display: block;
215+
// Wrapper for the hints and error messages. Provides positioning and text size.
216+
// Note that we're using `top` in order to allow for stacked children to flow downwards.
217+
.mat-input-hint-wrapper {
218218
position: absolute;
219219
font-size: 75%;
220-
bottom: 0;
220+
top: 100%;
221+
width: 100%;
222+
margin-top: -$mat-input-wrapper-spacing;
223+
}
224+
225+
// The hint is shown below the underline. There can be
226+
// more than one; one at the start and one at the end.
227+
.mat-hint {
228+
display: block;
229+
float: left;
221230

222231
&.mat-right {
223-
right: 0;
232+
float: right;
224233
}
225234

226235
[dir='rtl'] & {
227-
right: 0;
228-
left: auto;
236+
float: right;
229237

230238
&.mat-right {
231-
right: auto;
232-
left: 0;
239+
float: left;
233240
}
234241
}
235242
}
236243

244+
// Clears the floats on the hints. Necessary for the `transform` animation to work.
245+
.mat-input-hint-clearfix {
246+
&::before,
247+
&::after {
248+
content: '';
249+
display: table;
250+
}
251+
252+
&::after {
253+
clear: both;
254+
}
255+
}
256+
257+
// Single errror message displayed beneath the input.
258+
.mat-input-error {
259+
display: block;
260+
}
261+
237262
.mat-input-prefix, .mat-input-suffix {
238263
// Prevents the prefix and suffix from stretching together with the container.
239264
width: 0.1px;

0 commit comments

Comments
 (0)