1
votes

I know this has been asked many times, but after reading similar problems I'm still unable to organize my code to fix this exception,

in my component I have this property that change dynamically based on a condition

public emailToValue: string

in my html

the user can manually add a new row and then I call a pipe to set the value assigned in the component

<ng-container matColumnDef="emailTo">
<mat-header-cell *matHeaderCellDef mat-sort-header>Email To</mat-header-cell>
<mat-cell *matCellDef="let userMarket">
{{ userMarket | formatEmailTo : emailToValue}}
<input type="text"  matInput [value]="userMarket.emailTo">
</mat-cell>
</ng-container>

this is my pipe, if the email is null or undefined I set by default an email to be displayed in the new row

  @Pipe({
  name: 'formatEmailTo',
})
export class FormatEmailPipe implements PipeTransform {
  public transform(userMarket: UserMarketDTO, email: string): void {
    if (_.isNil(userMarket.emailTo)) {
    userMarket.emailTo = email;
    }
  }
}

functionality is working but Im getting this exception every time that I create a new row

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'value: undefined'. Current value: 'value: [email protected]'.

I would appreciate any help

thanks

1

1 Answers

0
votes

A couple points here:

  1. Functionality is only working in develop mode where change detection runs twice. When you get that error, that means that first value of undefined would have been the value on the front end in production.
  2. I'm not sure this is the best use of a pipe. I typically would expect a pipe to actually transform and return a value that the HTML needs. To me, it sounds like you should be defaulting your email value when the row gets added.

But assuming there's some requirement forcing you to do it this way and you want to avoid using the ChangeDetectorRef, I think I have a solution for you. Please see my stackblitz:

Use a template ref for the column with an already piped UserMarketDTO:

<ng-template #emailCell let-userMarket>
  {{ userMarket.email }}
  <input type="text" matInput [value]="userMarket.email">
<ng-template>

And the column definition where the piping happens:

<!-- Email Column -->
  <ng-container matColumnDef="email">
    <th mat-header-cell *matHeaderCellDef> Email </th>
    <td mat-cell *matCellDef="let element">
      <ng-container *ngTemplateOutlet="emailCell; context: { $implicit: element | formatEmailTo: defaultEmailValue }"></ng-container>
    </td>
  </ng-container>

But this also requires updating the pipe to return the UserMarketDTO:

public transform(userMarket: UserMarketDTO, defaultEmail: string): UserMarketDTO {
    if (!userMarket.email) {
      userMarket.email = defaultEmail;
    }
    return userMarket;
  }