Skip to content

Commit 6706708

Browse files
committed
fix: address more feedback
1 parent a9d144a commit 6706708

File tree

7 files changed

+89
-23
lines changed

7 files changed

+89
-23
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ <h4>Regular</h4>
6464

6565
<md-input-container>
6666
<input mdInput placeholder="email" [formControl]="emailFormControl">
67-
<md-error *ngIf="emailFormControl.errors ? emailFormControl.errors['required'] : false">
67+
<md-error *ngIf="emailFormControl.hasError('required')">
6868
This field is required
6969
</md-error>
70-
<md-error *ngIf="emailFormControl.errors ? emailFormControl.errors['pattern'] : false">
70+
<md-error *ngIf="emailFormControl.hasError('pattern')">
7171
Please enter a valid email address
7272
</md-error>
7373
</md-input-container>

src/lib/core/compatibility/compatibility.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ export const MAT_ELEMENTS_SELECTOR = `
7070
mat-spinner,
7171
mat-tab,
7272
mat-tab-group,
73-
mat-toolbar`;
73+
mat-toolbar,
74+
mat-error`;
7475

7576
/** Selector that matches all elements that may have style collisions with AngularJS Material. */
7677
export const MD_ELEMENTS_SELECTOR = `
@@ -130,7 +131,8 @@ export const MD_ELEMENTS_SELECTOR = `
130131
md-spinner,
131132
md-tab,
132133
md-tab-group,
133-
md-toolbar`;
134+
md-toolbar,
135+
md-error`;
134136

135137
/** Directive that enforces that the `mat-` prefix cannot be used. */
136138
@Directive({selector: MAT_ELEMENTS_SELECTOR})

src/lib/input/_input-theme.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@
6464
}
6565
}
6666

67+
// Styling for the error state of the input container. Note that while the same can be
68+
// achieved with the ng-* classes, we use this approach in order to ensure that the same
69+
// logic is used to style the error state and to show the error messages.
6770
.mat-input-invalid {
6871
.mat-input-placeholder,
6972
.mat-placeholder-required {

src/lib/input/input-container.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@
3737
</div>
3838

3939
<div class="mat-input-hint-wrapper" [ngSwitch]="_getDisplayedMessages()">
40-
<div *ngSwitchCase="'error'" [@transitionMessages]="_messageAnimationState">
40+
<div *ngSwitchCase="'error'" [@transitionMessages]="_subscriptAnimationState">
4141
<ng-content select="md-error, mat-error"></ng-content>
4242
</div>
4343

44-
<div *ngSwitchCase="'hint'" [@transitionMessages]="_messageAnimationState">
45-
<div *ngIf="hintLabel != ''" [attr.id]="_hintLabelId" class="mat-hint">{{hintLabel}}</div>
44+
<div *ngSwitchCase="'hint'" [@transitionMessages]="_subscriptAnimationState">
45+
<div *ngIf="hintLabel" [id]="_hintLabelId" class="mat-hint">{{hintLabel}}</div>
4646
<ng-content select="md-hint, mat-hint"></ng-content>
4747
</div>
4848
</div>

src/lib/input/input-container.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ $mat-input-underline-disabled-background-image:
237237
display: block;
238238
float: left;
239239

240+
// We use floats here, as opposed to flexbox, in order to make it
241+
// easier to reverse their location in rtl and to ensure that they're
242+
// aligned properly in some cases (e.g. when there is only an `end` hint).
240243
&.mat-right {
241244
float: right;
242245
}

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

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import {async, TestBed, inject, ComponentFixture} from '@angular/core/testing';
22
import {Component, ViewChild} from '@angular/core';
3-
import {FormsModule, ReactiveFormsModule, FormControl, NgForm, Validators} from '@angular/forms';
3+
import {
4+
FormsModule,
5+
ReactiveFormsModule,
6+
FormControl,
7+
NgForm,
8+
Validators,
9+
FormGroupDirective,
10+
FormGroup,
11+
} from '@angular/forms';
412
import {By} from '@angular/platform-browser';
513
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
614
import {MdInputModule} from './index';
@@ -54,7 +62,8 @@ describe('MdInputContainer', function () {
5462
MdInputContainerMultipleHintTestController,
5563
MdInputContainerMultipleHintMixedTestController,
5664
MdInputContainerWithDynamicPlaceholder,
57-
MdInputContainerWithErrorMessages
65+
MdInputContainerWithFormErrorMessages,
66+
MdInputContainerWithFormGroupErrorMessages
5867
],
5968
});
6069

@@ -556,19 +565,19 @@ describe('MdInputContainer', function () {
556565
});
557566

558567
describe('error messages', () => {
559-
let fixture: ComponentFixture<MdInputContainerWithErrorMessages>;
560-
let testComponent: MdInputContainerWithErrorMessages;
568+
let fixture: ComponentFixture<MdInputContainerWithFormErrorMessages>;
569+
let testComponent: MdInputContainerWithFormErrorMessages;
561570
let containerEl: HTMLElement;
562571

563572
beforeEach(() => {
564-
fixture = TestBed.createComponent(MdInputContainerWithErrorMessages);
573+
fixture = TestBed.createComponent(MdInputContainerWithFormErrorMessages);
565574
fixture.detectChanges();
566575
testComponent = fixture.componentInstance;
567576
containerEl = fixture.debugElement.query(By.css('md-input-container')).nativeElement;
568577
});
569578

570579
it('should not show any errors if the user has not interacted', () => {
571-
expect(testComponent.formControl.pristine).toBe(true, 'Expected untouched form control');
580+
expect(testComponent.formControl.untouched).toBe(true, 'Expected untouched form control');
572581
expect(containerEl.querySelectorAll('md-error').length).toBe(0, 'Expected no error messages');
573582
});
574583

@@ -604,6 +613,34 @@ describe('MdInputContainer', function () {
604613
});
605614
}));
606615

616+
it('should display an error message when the parent form group is submitted', async(() => {
617+
fixture.destroy();
618+
619+
let groupFixture = TestBed.createComponent(MdInputContainerWithFormGroupErrorMessages);
620+
let component: MdInputContainerWithFormGroupErrorMessages;
621+
622+
groupFixture.detectChanges();
623+
component = groupFixture.componentInstance;
624+
containerEl = groupFixture.debugElement.query(By.css('md-input-container')).nativeElement;
625+
626+
expect(component.formControl.invalid).toBe(true, 'Expected form control to be invalid');
627+
expect(containerEl.querySelectorAll('md-error').length).toBe(0, 'Expected no error messages');
628+
expect(component.formGroupDirective.submitted)
629+
.toBe(false, 'Expected form not to have been submitted');
630+
631+
dispatchFakeEvent(groupFixture.debugElement.query(By.css('form')).nativeElement, 'submit');
632+
groupFixture.detectChanges();
633+
634+
groupFixture.whenStable().then(() => {
635+
expect(component.formGroupDirective.submitted)
636+
.toBe(true, 'Expected form to have been submitted');
637+
expect(containerEl.classList)
638+
.toContain('mat-input-invalid', 'Expected container to have the invalid CSS class.');
639+
expect(containerEl.querySelectorAll('md-error').length)
640+
.toBe(1, 'Expected one error message to have been rendered.');
641+
});
642+
}));
643+
607644
it('should hide the error messages once the input becomes valid', async(() => {
608645
testComponent.formControl.markAsTouched();
609646
fixture.detectChanges();
@@ -909,8 +946,27 @@ class MdInputContainerMissingMdInputTestController {}
909946
</form>
910947
`
911948
})
912-
class MdInputContainerWithErrorMessages {
949+
class MdInputContainerWithFormErrorMessages {
913950
@ViewChild('form') form: NgForm;
914951
formControl = new FormControl('', Validators.required);
915952
renderError = true;
916953
}
954+
955+
956+
@Component({
957+
template: `
958+
<form [formGroup]="formGroup" (ngSubmit)="onSubmit()" novalidate>
959+
<md-input-container>
960+
<input mdInput [formControl]="formControl">
961+
<md-hint>Please type something</md-hint>
962+
<md-error>This field is required</md-error>
963+
</md-input-container>
964+
</form>
965+
`
966+
})
967+
class MdInputContainerWithFormGroupErrorMessages {
968+
@ViewChild(FormGroupDirective) formGroupDirective: FormGroupDirective;
969+
onSubmit() { }
970+
formControl = new FormControl('', Validators.required);
971+
formGroup = new FormGroup({ name: this.formControl });
972+
}

src/lib/input/input-container.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
trigger,
2525
} from '@angular/animations';
2626
import {coerceBooleanProperty} from '../core';
27-
import {NgControl, NgForm} from '@angular/forms';
27+
import {NgControl, NgForm, FormGroupDirective} from '@angular/forms';
2828
import {getSupportedInputTypes} from '../core/platform/features';
2929
import {
3030
MdInputContainerDuplicatedHintError,
@@ -257,7 +257,7 @@ export class MdInputDirective {
257257
state('enter', style({ opacity: 1, transform: 'translateY(0%)' })),
258258
transition('void => enter', [
259259
style({ opacity: 0, transform: 'translateY(-100%)' }),
260-
animate('300ms')
260+
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')
261261
])
262262
])
263263
],
@@ -292,7 +292,7 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit {
292292
get _canPlaceholderFloat() { return this._floatPlaceholder !== 'never'; }
293293

294294
/** State of the md-hint and md-error animations. */
295-
_messageAnimationState: string = '';
295+
_subscriptAnimationState: string = '';
296296

297297
/** Text for the input hint. */
298298
@Input()
@@ -324,7 +324,8 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit {
324324

325325
constructor(
326326
private _changeDetectorRef: ChangeDetectorRef,
327-
@Optional() private _parentForm: NgForm) { }
327+
@Optional() private _parentForm: NgForm,
328+
@Optional() private _parentFormGroup: FormGroupDirective) { }
328329

329330
ngAfterContentInit() {
330331
if (!this._mdInputChild) {
@@ -341,7 +342,7 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit {
341342

342343
ngAfterViewInit() {
343344
// Avoid animations on load.
344-
this._messageAnimationState = 'enter';
345+
this._subscriptAnimationState = 'enter';
345346
this._changeDetectorRef.detectChanges();
346347
}
347348

@@ -360,11 +361,12 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit {
360361
/** Whether the input container is in an error state. */
361362
_isErrorState(): boolean {
362363
const control = this._mdInputChild._ngControl;
363-
const isInvalid = control ? control.invalid : false;
364-
const isTouched = control ? control.touched : false;
365-
const isSubmitted = this._parentForm ? this._parentForm.submitted : false;
364+
const isInvalid = control && control.invalid;
365+
const isTouched = control && control.touched;
366+
const isSubmitted = (this._parentFormGroup && this._parentFormGroup.submitted) ||
367+
(this._parentForm && this._parentForm.submitted);
366368

367-
return isInvalid && (isTouched || isSubmitted);
369+
return !!(isInvalid && (isTouched || isSubmitted));
368370
}
369371

370372
/** Determines whether to display hints, errors or no messages at all. */

0 commit comments

Comments
 (0)