@@ -77,7 +77,6 @@ const _MatInputMixinBase: CanUpdateErrorStateCtor & typeof MatInputBase =
77
77
// Native input properties that are overwritten by Angular inputs need to be synced with
78
78
// the native input element. Otherwise property bindings for those don't work.
79
79
'[attr.id]' : 'id' ,
80
- '[attr.placeholder]' : '_getPlaceholderAttribute()' ,
81
80
// At the time of writing, we have a lot of customer tests that look up the input based on its
82
81
// placeholder. Since we sometimes omit the placeholder attribute from the DOM to prevent screen
83
82
// readers from reading it twice, we have to keep it somewhere in the DOM for the lookup.
@@ -96,6 +95,8 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
96
95
protected _uid = `mat-input-${ nextUniqueId ++ } ` ;
97
96
protected _previousNativeValue : any ;
98
97
private _inputValueAccessor : { value : any } ;
98
+ private _previousPlaceholder : string | null ;
99
+
99
100
/** The aria-describedby attribute on the input for improved a11y. */
100
101
_ariaDescribedby : string ;
101
102
@@ -315,6 +316,10 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
315
316
// we won't be notified when it changes (e.g. the consumer isn't using forms or they're
316
317
// updating the value using `emitEvent: false`).
317
318
this . _dirtyCheckNativeValue ( ) ;
319
+
320
+ // We need to dirty-check and set the placeholder attribute ourselves, because whether it's
321
+ // present or not depends on a query which is prone to "changed after checked" errors.
322
+ this . _dirtyCheckPlaceholder ( ) ;
318
323
}
319
324
320
325
/** Focuses the input. */
@@ -354,14 +359,21 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
354
359
// FormsModule or ReactiveFormsModule, because Angular forms also listens to input events.
355
360
}
356
361
357
- /** Determines the value of the native `placeholder` attribute that should be used in the DOM . */
358
- _getPlaceholderAttribute ( ) {
362
+ /** Does some manual dirty checking on the native input `placeholder` attribute. */
363
+ private _dirtyCheckPlaceholder ( ) {
359
364
// If we're hiding the native placeholder, it should also be cleared from the DOM, otherwise
360
365
// screen readers will read it out twice: once from the label and once from the attribute.
361
366
// TODO: can be removed once we get rid of the `legacy` style for the form field, because it's
362
367
// the only one that supports promoting the placeholder to a label.
363
368
const formField = this . _formField ;
364
- return ( ! formField || ! formField . _hideControlPlaceholder ( ) ) ? this . placeholder : undefined ;
369
+ const placeholder =
370
+ ( ! formField || ! formField . _hideControlPlaceholder ( ) ) ? this . placeholder : null ;
371
+ if ( placeholder !== this . _previousPlaceholder ) {
372
+ const element = this . _elementRef . nativeElement ;
373
+ this . _previousPlaceholder = placeholder ;
374
+ placeholder ?
375
+ element . setAttribute ( 'placeholder' , placeholder ) : element . removeAttribute ( 'placeholder' ) ;
376
+ }
365
377
}
366
378
367
379
/** Does some manual dirty checking on the native input `value` property. */
0 commit comments