@@ -77,7 +77,6 @@ const _MatInputMixinBase: CanUpdateErrorStateCtor & typeof MatInputBase =
7777 // Native input properties that are overwritten by Angular inputs need to be synced with
7878 // the native input element. Otherwise property bindings for those don't work.
7979 '[attr.id]' : 'id' ,
80- '[attr.placeholder]' : '_getPlaceholderAttribute()' ,
8180 // At the time of writing, we have a lot of customer tests that look up the input based on its
8281 // placeholder. Since we sometimes omit the placeholder attribute from the DOM to prevent screen
8382 // 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<
9695 protected _uid = `mat-input-${ nextUniqueId ++ } ` ;
9796 protected _previousNativeValue : any ;
9897 private _inputValueAccessor : { value : any } ;
98+ private _previousPlaceholder : string | null ;
99+
99100 /** The aria-describedby attribute on the input for improved a11y. */
100101 _ariaDescribedby : string ;
101102
@@ -315,6 +316,10 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
315316 // we won't be notified when it changes (e.g. the consumer isn't using forms or they're
316317 // updating the value using `emitEvent: false`).
317318 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 ( ) ;
318323 }
319324
320325 /** Focuses the input. */
@@ -354,14 +359,21 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
354359 // FormsModule or ReactiveFormsModule, because Angular forms also listens to input events.
355360 }
356361
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 ( ) {
359364 // If we're hiding the native placeholder, it should also be cleared from the DOM, otherwise
360365 // screen readers will read it out twice: once from the label and once from the attribute.
361366 // TODO: can be removed once we get rid of the `legacy` style for the form field, because it's
362367 // the only one that supports promoting the placeholder to a label.
363368 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+ }
365377 }
366378
367379 /** Does some manual dirty checking on the native input `value` property. */
0 commit comments