Skip to content

Commit feac08f

Browse files
crisbetoandrewseguin
authored andcommitted
fix(material/input): inconsistently reading name from input with ngModel (#19233)
If an input has a `name` binding and an `ngModel`, the input harness won't be able to read the name from the DOM, because `ngModel` doesn't proxy it. These changes add the proxy behavior to the `MatInput` directive, similarly to what we we're doing for `required`, `placeholder`, `readonly` etc. Fixes #18624. (cherry picked from commit 1926b19)
1 parent 70e0170 commit feac08f

File tree

4 files changed

+36
-13
lines changed

4 files changed

+36
-13
lines changed

src/material-experimental/mdc-input/input.ts

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {MatInput as BaseMatInput} from '@angular/material/input';
3737
'[id]': 'id',
3838
'[disabled]': 'disabled',
3939
'[required]': 'required',
40+
'[attr.name]': 'name',
4041
'[attr.placeholder]': 'placeholder',
4142
'[attr.readonly]': 'readonly && !_isNativeSelect || null',
4243
// Only mark the input as invalid for assistive technology if it has a value since the

src/material/input/input.ts

+7
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ const _MatInputBase = mixinErrorState(
7878
'[attr.data-placeholder]': 'placeholder',
7979
'[disabled]': 'disabled',
8080
'[required]': 'required',
81+
'[attr.name]': 'name || null',
8182
'[attr.readonly]': 'readonly && !_isNativeSelect || null',
8283
'[class.mat-native-select-inline]': '_isInlineSelect()',
8384
// Only mark the input as invalid for assistive technology if it has a value since the
@@ -183,6 +184,12 @@ export class MatInput
183184
*/
184185
@Input() placeholder: string;
185186

187+
/**
188+
* Name of the input.
189+
* @docs-private
190+
*/
191+
@Input() name: string;
192+
186193
/**
187194
* Implemented as part of MatFormFieldControl.
188195
* @docs-private

src/material/input/testing/shared-input.spec.ts

+26-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {HarnessLoader} from '@angular/cdk/testing';
22
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
33
import {Component} from '@angular/core';
44
import {ComponentFixture, TestBed} from '@angular/core/testing';
5-
import {ReactiveFormsModule} from '@angular/forms';
5+
import {FormsModule} from '@angular/forms';
66
import {MatInputModule} from '@angular/material/input';
77
import {getSupportedInputTypes} from '@angular/cdk/platform';
88
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
@@ -18,7 +18,7 @@ export function runInputHarnessTests(
1818

1919
beforeEach(async () => {
2020
await TestBed.configureTestingModule({
21-
imports: [NoopAnimationsModule, inputModule, ReactiveFormsModule],
21+
imports: [NoopAnimationsModule, inputModule, FormsModule],
2222
declarations: [InputHarnessTest],
2323
}).compileComponents();
2424

@@ -29,7 +29,7 @@ export function runInputHarnessTests(
2929

3030
it('should load all input harnesses', async () => {
3131
const inputs = await loader.getAllHarnesses(inputHarness);
32-
expect(inputs.length).toBe(6);
32+
expect(inputs.length).toBe(7);
3333
});
3434

3535
it('should load input with specific id', async () => {
@@ -68,37 +68,40 @@ export function runInputHarnessTests(
6868

6969
it('should be able to get id of input', async () => {
7070
const inputs = await loader.getAllHarnesses(inputHarness);
71-
expect(inputs.length).toBe(6);
71+
expect(inputs.length).toBe(7);
7272
expect(await inputs[0].getId()).toMatch(/mat-input-\d+/);
7373
expect(await inputs[1].getId()).toMatch(/mat-input-\d+/);
7474
expect(await inputs[2].getId()).toBe('myTextarea');
7575
expect(await inputs[3].getId()).toBe('nativeControl');
7676
expect(await inputs[4].getId()).toMatch(/mat-input-\d+/);
77+
expect(await inputs[5].getId()).toBe('has-ng-model');
7778
});
7879

7980
it('should be able to get name of input', async () => {
8081
const inputs = await loader.getAllHarnesses(inputHarness);
81-
expect(inputs.length).toBe(6);
82+
expect(inputs.length).toBe(7);
8283
expect(await inputs[0].getName()).toBe('favorite-food');
8384
expect(await inputs[1].getName()).toBe('');
8485
expect(await inputs[2].getName()).toBe('');
8586
expect(await inputs[3].getName()).toBe('');
8687
expect(await inputs[4].getName()).toBe('');
88+
expect(await inputs[5].getName()).toBe('has-ng-model');
8789
});
8890

8991
it('should be able to get value of input', async () => {
9092
const inputs = await loader.getAllHarnesses(inputHarness);
91-
expect(inputs.length).toBe(6);
93+
expect(inputs.length).toBe(7);
9294
expect(await inputs[0].getValue()).toBe('Sushi');
9395
expect(await inputs[1].getValue()).toBe('');
9496
expect(await inputs[2].getValue()).toBe('');
9597
expect(await inputs[3].getValue()).toBe('');
9698
expect(await inputs[4].getValue()).toBe('');
99+
expect(await inputs[5].getValue()).toBe('');
97100
});
98101

99102
it('should be able to set value of input', async () => {
100103
const inputs = await loader.getAllHarnesses(inputHarness);
101-
expect(inputs.length).toBe(6);
104+
expect(inputs.length).toBe(7);
102105
expect(await inputs[0].getValue()).toBe('Sushi');
103106
expect(await inputs[1].getValue()).toBe('');
104107
expect(await inputs[3].getValue()).toBe('');
@@ -117,13 +120,14 @@ export function runInputHarnessTests(
117120

118121
it('should be able to get disabled state', async () => {
119122
const inputs = await loader.getAllHarnesses(inputHarness);
120-
expect(inputs.length).toBe(6);
123+
expect(inputs.length).toBe(7);
121124

122125
expect(await inputs[0].isDisabled()).toBe(false);
123126
expect(await inputs[1].isDisabled()).toBe(false);
124127
expect(await inputs[2].isDisabled()).toBe(false);
125128
expect(await inputs[3].isDisabled()).toBe(false);
126129
expect(await inputs[4].isDisabled()).toBe(false);
130+
expect(await inputs[5].isDisabled()).toBe(false);
127131

128132
fixture.componentInstance.disabled = true;
129133

@@ -132,13 +136,14 @@ export function runInputHarnessTests(
132136

133137
it('should be able to get readonly state', async () => {
134138
const inputs = await loader.getAllHarnesses(inputHarness);
135-
expect(inputs.length).toBe(6);
139+
expect(inputs.length).toBe(7);
136140

137141
expect(await inputs[0].isReadonly()).toBe(false);
138142
expect(await inputs[1].isReadonly()).toBe(false);
139143
expect(await inputs[2].isReadonly()).toBe(false);
140144
expect(await inputs[3].isReadonly()).toBe(false);
141145
expect(await inputs[4].isReadonly()).toBe(false);
146+
expect(await inputs[5].isReadonly()).toBe(false);
142147

143148
fixture.componentInstance.readonly = true;
144149

@@ -147,13 +152,14 @@ export function runInputHarnessTests(
147152

148153
it('should be able to get required state', async () => {
149154
const inputs = await loader.getAllHarnesses(inputHarness);
150-
expect(inputs.length).toBe(6);
155+
expect(inputs.length).toBe(7);
151156

152157
expect(await inputs[0].isRequired()).toBe(false);
153158
expect(await inputs[1].isRequired()).toBe(false);
154159
expect(await inputs[2].isRequired()).toBe(false);
155160
expect(await inputs[3].isRequired()).toBe(false);
156161
expect(await inputs[4].isRequired()).toBe(false);
162+
expect(await inputs[5].isRequired()).toBe(false);
157163

158164
fixture.componentInstance.required = true;
159165

@@ -162,22 +168,24 @@ export function runInputHarnessTests(
162168

163169
it('should be able to get placeholder of input', async () => {
164170
const inputs = await loader.getAllHarnesses(inputHarness);
165-
expect(inputs.length).toBe(6);
171+
expect(inputs.length).toBe(7);
166172
expect(await inputs[0].getPlaceholder()).toBe('Favorite food');
167173
expect(await inputs[1].getPlaceholder()).toBe('');
168174
expect(await inputs[2].getPlaceholder()).toBe('Leave a comment');
169175
expect(await inputs[3].getPlaceholder()).toBe('Native control');
170176
expect(await inputs[4].getPlaceholder()).toBe('');
177+
expect(await inputs[5].getPlaceholder()).toBe('');
171178
});
172179

173180
it('should be able to get type of input', async () => {
174181
const inputs = await loader.getAllHarnesses(inputHarness);
175-
expect(inputs.length).toBe(6);
182+
expect(inputs.length).toBe(7);
176183
expect(await inputs[0].getType()).toBe('text');
177184
expect(await inputs[1].getType()).toBe('number');
178185
expect(await inputs[2].getType()).toBe('textarea');
179186
expect(await inputs[3].getType()).toBe('text');
180187
expect(await inputs[4].getType()).toBe('textarea');
188+
expect(await inputs[5].getType()).toBe('text');
181189

182190
fixture.componentInstance.inputType = 'text';
183191

@@ -248,6 +256,10 @@ export function runInputHarnessTests(
248256
</select>
249257
</mat-form-field>
250258
259+
<mat-form-field>
260+
<input [(ngModel)]="ngModelValue" [name]="ngModelName" id="has-ng-model" matNativeControl>
261+
</mat-form-field>
262+
251263
<mat-form-field>
252264
<input matNativeControl placeholder="Color control" id="colorControl" type="color">
253265
</mat-form-field>
@@ -258,4 +270,6 @@ class InputHarnessTest {
258270
readonly = false;
259271
disabled = false;
260272
required = false;
273+
ngModelValue = '';
274+
ngModelName = 'has-ng-model';
261275
}

tools/public_api_guard/material/input.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export class MatInput extends _MatInputBase implements MatFormFieldControl<any>,
6565
protected _isNeverEmpty(): boolean;
6666
readonly _isServer: boolean;
6767
readonly _isTextarea: boolean;
68+
name: string;
6869
// (undocumented)
6970
protected _neverEmptyInputTypes: string[];
7071
// (undocumented)
@@ -103,7 +104,7 @@ export class MatInput extends _MatInputBase implements MatFormFieldControl<any>,
103104
get value(): string;
104105
set value(value: any);
105106
// (undocumented)
106-
static ɵdir: i0.ɵɵDirectiveDeclaration<MatInput, "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", ["matInput"], { "disabled": "disabled"; "id": "id"; "placeholder": "placeholder"; "required": "required"; "type": "type"; "errorStateMatcher": "errorStateMatcher"; "userAriaDescribedBy": "aria-describedby"; "value": "value"; "readonly": "readonly"; }, {}, never>;
107+
static ɵdir: i0.ɵɵDirectiveDeclaration<MatInput, "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", ["matInput"], { "disabled": "disabled"; "id": "id"; "placeholder": "placeholder"; "name": "name"; "required": "required"; "type": "type"; "errorStateMatcher": "errorStateMatcher"; "userAriaDescribedBy": "aria-describedby"; "value": "value"; "readonly": "readonly"; }, {}, never>;
107108
// (undocumented)
108109
static ɵfac: i0.ɵɵFactoryDeclaration<MatInput, [null, null, { optional: true; self: true; }, { optional: true; }, { optional: true; }, null, { optional: true; self: true; }, null, null, { optional: true; }]>;
109110
}

0 commit comments

Comments
 (0)