Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mat-hint causing an ExpressionChangedAfterItHasBeenCheckedError if respective input has a *ngIf directive #16209

Closed
jspieker opened this issue Jun 5, 2019 · 22 comments · Fixed by #24644
Labels
area: material/form-field P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent

Comments

@jspieker
Copy link

jspieker commented Jun 5, 2019

Reproduction

Minimal repro on StackBlitz. Open the console to see the following error message:

Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'aria-describedby: null'. Current value: 'aria-describedby: mat-hint-0'.

This always happens if the respective input has a *ngIf-directive. I couldn't observe any subsequent errors caused by this. Using the hintLabel property instead of a <mat-hint> results in the same error.

Expected Behavior

No ExpressionChangedAfterItHasBeenCheckedError when using *ngIf on an input

Actual Behavior

ExpressionChangedAfterItHasBeenCheckedError

Environment

  • Angular: ^7.1.4
  • CDK/Material: ^7.2.0
  • Browser(s): Chrome (Most recent @ Version 75.0.3770.80)
  • Operating System (e.g. Windows, macOS, Ubuntu): macOS
@tomicarsk6
Copy link
Contributor

tomicarsk6 commented Jun 5, 2019

This is because Angular is running in development mode, and in this mode change detection adds an additional turn after every regular change detection run to check if the model has changed.

If you run this example locally on you machine in production mode, everything will be ok.
Screen Shot 2019-06-05 at 10 43 12

@jspieker jspieker closed this as completed Jun 5, 2019
@heneryville
Copy link

This should not be marked as closed. While true that running in production mode won't raise this error, that's just because production is no longer running with this safety check on (for efficiency). The bug still exists and should still be addressed.

@jspieker jspieker reopened this Jun 19, 2019
@ritxweb
Copy link

ritxweb commented Jul 19, 2019

Same error for same reason, any news?

Thanks!

@ritxweb
Copy link

ritxweb commented Jul 19, 2019

Hello

I am having this error using mat-form-field with several inputs with a *ngIf each:

<mat-form-field> <input *ngIf="displayLanguage === 1" matInput type="text" formControlName="city" name="city" required> <input *ngIf="displayLanguage === 2" matInput type="text" formControlName="city2" name="city2"> <input *ngIf="displayLanguage === 3" matInput type="text" formControlName="city3" name="city3"> </mat-form-field>

But when I create a mat-form-field for each input and apply the *ngIf to them, the error disappears:

<mat-form-field *ngIf="displayLanguage === 1"> <input matInput type="text" formControlName="city" name="city" required> </mat-form-field> <mat-form-field *ngIf="displayLanguage === 2"> <input matInput type="text" formControlName="city2" name="city2"> </mat-form-field> <mat-form-field *ngIf="displayLanguage === 3"> <input matInput type="text" formControlName="city3" name="city3"> </mat-form-field>

Hope this helps.

@joaodforce
Copy link

joaodforce commented Jul 22, 2019

Hello

I am having this error using mat-form-field with several inputs with a *ngIf each:

<mat-form-field> <input *ngIf="displayLanguage === 1" matInput type="text" formControlName="city" name="city" required> <input *ngIf="displayLanguage === 2" matInput type="text" formControlName="city2" name="city2"> <input *ngIf="displayLanguage === 3" matInput type="text" formControlName="city3" name="city3"> </mat-form-field>

But when I create a mat-form-field for each input and apply the *ngIf to them, the error disappears:

<mat-form-field *ngIf="displayLanguage === 1"> <input matInput type="text" formControlName="city" name="city" required> </mat-form-field> <mat-form-field *ngIf="displayLanguage === 2"> <input matInput type="text" formControlName="city2" name="city2"> </mat-form-field> <mat-form-field *ngIf="displayLanguage === 3"> <input matInput type="text" formControlName="city3" name="city3"> </mat-form-field>

Hope this helps.

I'm also having this same issue.
I'm using it the same way as @ritxweb was however I believe using it this way should not raise this error.

Mainly because I've made a dynamic form builder and every generated component shared the name component, and if I were to put one on each field condition, I would also have to put one , copying and pasting the same code over and over.

PowerKiKi added a commit to Ecodev/natural that referenced this issue Aug 30, 2019
Unfortunately because of angular/components#16209
we cannot very well use *ngIf inside a <mat-form-field> so we have no choice
but duplicate the whole thing and maintain both copies in parallel
@PowerKiKi
Copy link
Contributor

#8626 is somewhat related as it could have been a workaround for this issue, but it does not work either as explained over there.

@Phil147
Copy link

Phil147 commented Oct 1, 2019

We are building a component library which is very inspired by angular material :) we use the concept of the formfield with some modifications but run into the same issue.

What I found out is that with the ngIf on the input the instantiation changes.

With ngIf the cycle is like:

  • formfield gets instantiated
  • input gets instantiated
  • because of the ngIf the inputs view gets already initialized and checked, so the describedby is null
  • formfield content init here the described by ids get set now
  • formfield view init + checked -> error because the aria-described by of the input has changed

Without ngif the cycle is:

  • formfield gets instantiated
  • input gets instantiated
  • formfield content init -> ids get set on the input
  • input view init + checked -> no error

A possible solution I found is to defer all calls of _syncDescribedByIds to the next CD cycle. The downside I see is additional CD cycles especially during the initialisation of the formfield because it is called in the subscription of control.stateChanges, hintChildren.changes and errorChildren.changes which all are emitted during the first initialisation of the formfield. But maybe that can be mitigated somehow.

@andreElrico
Copy link

That a bug,

im running into the same problem

<mat-form-field style="width: 100%;" [ngClass]="{myInputNoWrapper: options.noWrapper}">  
  <mat-label *ngIf="options.label">{{options.label}}</mat-label>
  <span *ngIf="options.prefix" matPrefix>{{ options.prefix }} &#160; </span>

  <input *ngIf="options.type != 'textarea'; else ta" matInput [formControl]="myInputControl"
  (input)="onChange($event)"
  (blur)="onBlur($event)"
  [type]="options.type" [placeholder]="options.placeholder"
  [required]="options.required">

  <ng-template #ta>
    <textarea [ngStyle]="{'height': options.textareaH}" matInput [formControl]="myInputControl"
    (input)="onChange($event)"
    (blur)="onBlur($event)"
    [placeholder]="options.placeholder"
    [required]="options.required"
    ></textarea>
  </ng-template>  

  <mat-hint *ngIf="options.hint">{{options.hint}}</mat-hint>
  <mat-error *ngIf="!options.noValidate" [MYDIRECTIVE]="myInputControl"></mat-error>
</mat-form-field>

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'aria-describedby: null'. Current value: 'aria-describedby: mat-hint-0'

if the only workaround is code duplication ...

@shirados90
Copy link

The same problem appears with both mat-hint and mat-error elements.
To avoid ExpressionChangedAfterItHasBeenCheckedError beeing thrown you should avoid using *ngIf directives with matInput elements

@konrad-garus
Copy link

"Avoid using *ngIf" is not a solution at all. It's a pretty essential feature of Angular, you can't just avoid it everywhere above your mat-error/mat-hint.

@joaodforce
Copy link

"Avoid using *ngIf" is not a solution at all. It's a pretty essential feature of Angular, you can't just avoid it everywhere above your mat-error/mat-hint.

I think its not about avoiding ngIf its about learning when it is pertinent or not.

when an ngIf statement is false, the component ceases to Exist in DOM. and if it doesn't exist when it is needed then the library will crash (although this could be treated better internally), using a <ng-template> for the mat-error component is better, because the component will always be there available, and can be referenced by the library.

Every time a ngIf statement goes from false to true the component will be created form scratch, which sometimes can lead to performance issues. Thats why its better to learn when it is ok or not to use it.

@konrad-garus
Copy link

@joaodforce Thank you so much, it has actually led me to a much better solution!

However, I'd still say there is a but in angular-material here, and I'm not sure if avoiding ngIf/ngFor is always possible. I could not get it to work within mat-stepper, which actually relies on step components being in DOM while I need to dynamically control what steps there are.

Here's another workaround:

@Component()
class MyComponentWithInputField implements AfterViewInit {
  myControl: AbstractControl;

  constructor(private cdr: ChangeDetectorRef) { }

  ngAfterViewInit() {
    if (this.myControl.errors) {
      this.cdr.detectChanges();
    }
  }
}

@zlodeev
Copy link

zlodeev commented May 13, 2020

Try use Unique ID for the hint. A simple hack to make the error go away
stackblitz

<mat-hint [id]="null">No error</mat-hint>

@mmalerba mmalerba added the needs triage This issue needs to be triaged by the team label May 20, 2020
@crisbeto crisbeto added area: material/form-field P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent and removed needs triage This issue needs to be triaged by the team labels May 25, 2020
@matthewerwin
Copy link

Would be wonderful for this to be addressed.

A use-case where an *ngIf would be needed is in a case dealing with Countries and States --where a text-entry of State is allowed but then switches to a drop-down for certain countries (e.g. US) as follows:

 <mat-form-field appearance="outline">
        <mat-label>State/Province/Region</mat-label>
        <input matInput type="text" required formControlName="state_province" *ngIf="stateProvinceRegions.length == 0; else stateProvinceRegionSelector"/>
        <ng-template #stateProvinceRegionSelector>
          <mat-select required formControlName="state_province">
            <mat-option *ngFor="let entry of stateProvinceRegions" [value]="entry.key" >
              {{entry.value}}
            </mat-option>
          </mat-select>
        </ng-template>
        <!-- [id]="null" is a hack (https://github.com/angular/components/issues/16209)-->
        <mat-error [id]="null" *ngIf="purchaseForm.get('address.state_province')?.errors?.required">Region is required.</mat-error>
 </mat-form-field>

@mjamin
Copy link

mjamin commented Jul 18, 2020

I suspect this would be solved by addressing this framework issue: angular/angular#37319

@huibk89
Copy link

huibk89 commented Oct 22, 2020

for now i still have the same problem.
Fastest fix for me was instead of using *ngIf for the matInput's, use fxHide.
ofc you need to be using the flexLayout module, but simulair solutions should work.

@aminesafi8
Copy link

for now i still have the same problem.
Fastest fix for me was instead of using *ngIf for the matInput's, use fxHide.
ofc you need to be using the flexLayout module, but simulair solutions should work.

This issue can be solved simply by injecting into the constructor private cdRef: ChangeDetectorRef
And in the ngAfterViewInit cycle hook just call the method detectChanges()
ngAfterViewInit(): void { this.cdRef.detectChanges(); }

@austenstone
Copy link

austenstone commented Mar 13, 2021

Error:

<mat-hint *ngIf="dropdownOptions.hint">{{dropdownOptions.hint}}</mat-hint>

No Error using @zlodeev 's answer.

<mat-hint *ngIf="dropdownOptions.hint" [id]="null">{{dropdownOptions.hint}}</mat-hint>

@spali
Copy link

spali commented May 6, 2021

Error:

<mat-hint *ngIf="dropdownOptions.hint">{{dropdownOptions.hint}}</mat-hint>

No Error using @zlodeev 's answer.

<mat-hint *ngIf="dropdownOptions.hint" [id]="null">{{dropdownOptions.hint}}</mat-hint>

works with mat-error too and also works with [id]="''" because of the typechecking for string ;)

@BruneXX
Copy link

BruneXX commented Aug 31, 2021

Hi Guys regarding #8626 and this issue, there's currently no way to use

<mat-form-field>
   <ng-container *ngTemplateOutlet="myFuncyTemplateWithACustomElement"></ng-container>
</mat-form-field>

Do you see a good workaround here to avoid code duplication into the component template?
Shoudl we report a request for an angular feature here?

@RobertAKARobin
Copy link
Contributor

RobertAKARobin commented Jan 27, 2022

This may have been fixed by #19587 for MatInput. Subsequent PRs will be needed for MatChipList (#24292) and MatSelect.

RobertAKARobin added a commit to RobertAKARobin/components that referenced this issue Jan 31, 2022
Fixes angular#16209: ExpressionChangedAfterItHasBeenCheckedError that occurs when chip-lists are content-projected into a mat-form-field
amysorto pushed a commit to amysorto/components that referenced this issue Feb 15, 2022
…gular#24292)

Fixes angular#16209: ExpressionChangedAfterItHasBeenCheckedError that occurs when chip-lists are content-projected into a mat-form-field
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Mar 17, 2022
forsti0506 pushed a commit to forsti0506/components that referenced this issue Apr 3, 2022
…gular#24292)

Fixes angular#16209: ExpressionChangedAfterItHasBeenCheckedError that occurs when chip-lists are content-projected into a mat-form-field
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: material/form-field P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
Projects
None yet
Development

Successfully merging a pull request may close this issue.