diff --git a/src/material/input/input.spec.ts b/src/material/input/input.spec.ts index 2680893a1046..72a73765466f 100644 --- a/src/material/input/input.spec.ts +++ b/src/material/input/input.spec.ts @@ -996,6 +996,12 @@ describe('MatInput without forms', () => { }).not.toThrow(); })); + it('should not throw when there is a default ngIf on the input element', fakeAsync(() => { + expect(() => { + createComponent(MatInputWithAnotherNgIf).detectChanges(); + }).not.toThrow(); + })); + }); describe('MatInput with forms', () => { @@ -2251,3 +2257,18 @@ class CustomMatInputAccessor { ` }) class MatInputWithDefaultNgIf {} + + +// Note that the DOM structure is slightly weird, but it's +// testing a specific g3 issue. See the discussion on #10466. +@Component({ + template: ` + + App name + + + ` +}) +class MatInputWithAnotherNgIf { + inputValue = 'test'; +} diff --git a/src/material/input/input.ts b/src/material/input/input.ts index d096ff2148ed..f567a71eb5f5 100644 --- a/src/material/input/input.ts +++ b/src/material/input/input.ts @@ -77,7 +77,6 @@ const _MatInputMixinBase: CanUpdateErrorStateCtor & typeof MatInputBase = // Native input properties that are overwritten by Angular inputs need to be synced with // the native input element. Otherwise property bindings for those don't work. '[attr.id]': 'id', - '[attr.placeholder]': '_getPlaceholderAttribute()', // At the time of writing, we have a lot of customer tests that look up the input based on its // placeholder. Since we sometimes omit the placeholder attribute from the DOM to prevent screen // 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< protected _uid = `mat-input-${nextUniqueId++}`; protected _previousNativeValue: any; private _inputValueAccessor: {value: any}; + private _previousPlaceholder: string | null; + /** The aria-describedby attribute on the input for improved a11y. */ _ariaDescribedby: string; @@ -315,6 +316,10 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl< // we won't be notified when it changes (e.g. the consumer isn't using forms or they're // updating the value using `emitEvent: false`). this._dirtyCheckNativeValue(); + + // We need to dirty-check and set the placeholder attribute ourselves, because whether it's + // present or not depends on a query which is prone to "changed after checked" errors. + this._dirtyCheckPlaceholder(); } /** Focuses the input. */ @@ -354,14 +359,21 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl< // FormsModule or ReactiveFormsModule, because Angular forms also listens to input events. } - /** Determines the value of the native `placeholder` attribute that should be used in the DOM. */ - _getPlaceholderAttribute() { + /** Does some manual dirty checking on the native input `placeholder` attribute. */ + private _dirtyCheckPlaceholder() { // If we're hiding the native placeholder, it should also be cleared from the DOM, otherwise // screen readers will read it out twice: once from the label and once from the attribute. // TODO: can be removed once we get rid of the `legacy` style for the form field, because it's // the only one that supports promoting the placeholder to a label. const formField = this._formField; - return (!formField || !formField._hideControlPlaceholder()) ? this.placeholder : undefined; + const placeholder = + (!formField || !formField._hideControlPlaceholder()) ? this.placeholder : null; + if (placeholder !== this._previousPlaceholder) { + const element = this._elementRef.nativeElement; + this._previousPlaceholder = placeholder; + placeholder ? + element.setAttribute('placeholder', placeholder) : element.removeAttribute('placeholder'); + } } /** Does some manual dirty checking on the native input `value` property. */ diff --git a/tools/public_api_guard/material/input.d.ts b/tools/public_api_guard/material/input.d.ts index e29db030c40c..27a888c6d481 100644 --- a/tools/public_api_guard/material/input.d.ts +++ b/tools/public_api_guard/material/input.d.ts @@ -43,7 +43,6 @@ export declare class MatInput extends _MatInputMixinBase implements MatFormField ngControl: NgControl, _parentForm: NgForm, _parentFormGroup: FormGroupDirective, _defaultErrorStateMatcher: ErrorStateMatcher, inputValueAccessor: any, _autofillMonitor: AutofillMonitor, ngZone: NgZone, _formField?: MatFormField | undefined); protected _dirtyCheckNativeValue(): void; _focusChanged(isFocused: boolean): void; - _getPlaceholderAttribute(): string | undefined; protected _isBadInput(): boolean; protected _isNeverEmpty(): boolean; _onInput(): void;