I recently ran into a bug that mat-hint threw an error when its corresponding matInput is inserted conditionally. E.g. take the following template:
<mat-form-field>
<input *ngIf="isFile()" matInput placeholder="Enter file name" formControlName="filename">
<input *ngIf="isDirectory()" matInput placeholder="Enter directory name" formControlName="filename">
<mat-hint>
Some hint.
</mat-hint>
</mat-form-field>
Note that the only reason inputs are inserted conditionally here is because the placeholder text is different depending on the condition.
This will throw the following error:
app.component.html:7 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'null'. Current value: 'mat-hint-0'.
at new d (VM8254 zone.min.js:1)
at viewDebugError (VM7606 core.umd.js:8466)
at expressionChangedAfterItHasBeenCheckedError (VM7606 core.umd.js:8444)
at checkBindingNoChanges (VM7606 core.umd.js:8608)
at checkNoChangesNodeDynamic (VM7606 core.umd.js:12515)
at checkNoChangesNode (VM7606 core.umd.js:12464)
at debugCheckNoChangesNode (VM7606 core.umd.js:13241)
at debugCheckRenderNodeFn (VM7606 core.umd.js:13181)
at Object.eval [as updateRenderer] (VM7993 AppComponent.ngfactory.js:78)
at Object.debugUpdateRenderer [as updateRenderer] (VM7606 core.umd.js:13163)
at checkNoChangesView (VM7606 core.umd.js:12283)
at callViewAction (VM7606 core.umd.js:12650)
at execEmbeddedViewsAction (VM7606 core.umd.js:12628)
at checkNoChangesView (VM7606 core.umd.js:12282)
at callViewAction (VM7606 core.umd.js:12650)
This is not a problem when the input is not inserted conditionally but rather placed in the template right away. I'm not sure if that is by design or not but it caused me some hours of debugging until I realised it was the combination of mat-hint inside a mat-form-field whom\s matInput is rendered using *ngIf.
I expected this to just work, but again, maybe this is by design but then we should document somewhere that matInputs aren't allowed to be inserted using structural directives.
Described above.
Here's a plunk that reproduces the error: http://plnkr.co/edit/MrUvFVKIW1BoX9d96eeu?p=preview
Simply check the console.
I'm using @angular/material version 2.0.0-beta.11 but the plunk uses the latest version. Unfortunately I can't tell if this error has been thrown in older versions as well.
Yeah, you're all doing an amazing job. Thanks for that.
Rather than conditional <input> elements, I suggest just setting up a dynamic placeholder binding:
html:
<input [placeholder]="myPlaceholder"
typescript:
myPlaceholder = 'dynamic placeholder'
Though I was planning on looking into making form-field pieces more swappable
@mmalerba while your suggestion will work with a normal string, but I don't see it usable with the current i18n implementation of angular for different languages. 馃槥 Or am I missing something?
(while you are thinking about "form-field pieces that are more swappable" it would be really useful for me that the <mat-form-field> recognizes matInputs that are placed via <ng-content> in it. Here a stackblitz with an example of what I mean...)
I am having this issue too, but it is with mat-error instead of mat-hint:
<input *ngIf="!structure[key].options" matInput [formControlName]="key"
[placeholder]="structure[key].label">
<mat-select *ngIf="structure[key].options"
[placeholder]="structure[key].label" [formControlName]="key">
<mat-option *ngFor="let value of structure[key].options"
[value]="value.value">{{value.label}}</mat-option>
</mat-select>
You can localize a placeholder using:
<mat-form-field>
<input *ngIf="isFile()" matInput i18n-placeholder placeholder="Enter file name" formControlName="filename">
<input *ngIf="isDirectory()" matInput i18n-placeholder placeholder="Enter directory name" formControlName="filename">
</mat-form-field>
But not the most DRY approach. According to the documentation:
_"The placeholder can be specified either via a placeholder attribute on the input or a
https://material.angular.io/components/input/overview
So a better way of localizing multiple types of placeholders would be:
<mat-form-field>
<mat-placeholder i18n *ngIf="isFile()">Enter file name</mat-placeholder>
<mat-placeholder i18n *ngIf="isDirectory()">Enter directory name</mat-placeholder>
<input matInput formControlName="filename" />
</mat-form-field>
And that might solve your errors too as you will only have the single input?
Hitting the same issue with mat-hint and ngSwitch:
<mat-form-field
*ngFor="let field of schema.fields"
floatLabel="always"
[hintLabel]="field.hint">
<ng-container [ngSwitch]="field.type || 'text'">
<input matInput
*ngSwitchCase="'text'"
[placeholder]="field.placeholder"
[formControlName]="field.name"
[errorStateMatcher]="matcher"
[required]="field.required">
<mat-select
*ngSwitchCase="'select'"
[placeholder]="field.placeholder"
[formControlName]="field.name"
[errorStateMatcher]="matcher"
[required]="field.required">
</mat-select>
</ng-container>
</mat-form-field>
Tried switching to using <mat-hint> element instead to no avail:
<mat-form-field
*ngFor="let field of schema.fields"
floatLabel="always">
<mat-hint
align="start">
{{ field.hint }}
</mat-hint>
<ng-container [ngSwitch]="field.type || 'text'">
<input matInput
*ngSwitchCase="'text'"
[placeholder]="field.placeholder"
[formControlName]="field.name"
[errorStateMatcher]="matcher"
[required]="field.required">
<mat-select
*ngSwitchCase="'select'"
[placeholder]="field.placeholder"
[formControlName]="field.name"
[errorStateMatcher]="matcher"
[required]="field.required">
</mat-select>
</ng-container>
</mat-form-field>
Would definitely love to see "more swappable form-field pieces" 馃檹
P.S. thanks to the Angular team for the good work they've done so far 鉂わ笍 馃憦
__Update__: was able to workaround by grouping the all form components together into a single conditional render -- basically render the whole mat-form-field and its children together (not DRY but it works):
<ng-container *ngFor="let field of schema.fields">
<ng-container [ngSwitch]="field.type || 'text'">
<ng-container *ngSwitchCase="'text'">
<mat-form-field floatLabel="always">
<input matInput
[placeholder]="field.placeholder"
[formControlName]="field.name"
[errorStateMatcher]="matcher"
[required]="field.required">
<mat-hint>
{{ field.hint }}
</mat-hint>
</mat-form-field>
</ng-container>
<ng-container *ngSwitchCase="'select'">
<mat-form-field floatLabel="always">
<mat-select
[placeholder]="field.placeholder"
[formControlName]="field.name"
[errorStateMatcher]="matcher"
[required]="field.required">
</mat-select>
<mat-hint>
{{ field.hint }}
</mat-hint>
</mat-form-field>
</ng-container>
</ng-container>
</ng-container>
Also suffering hard from this issue, requiring me to have a lot of redundant code, as errors and hints cannot be build together dynamically!
Any input on this issue from the Angular team?
I have an open question on stackoverflow where I encountered the same issue. In my case I need to dynamically swap two inputs: one that does and one that does not have have a custom errorStateMatcher.
It seems to be the same as https://github.com/angular/components/issues/16209
Try use Unique ID for the hint. A simple hack to make the error go away
<mat-hint [id]="field.name">No error</mat-hint>
Most helpful comment
I am having this issue too, but it is with mat-error instead of mat-hint: