Tips for effectively using ngOnChanges in Angular 2 to validate inputs without causing the 'Expression has changed after it was checked' error

I attempted to create my own custom component with basic validation using regex that can be passed as input to the component. There are two scenarios to consider: one where the form is initially empty (new item form) and another where data is already present (edit).

The issue arises when I try to validate fields after the data has been set, which often results in the error 'Expression has changed after it was checked.'. I understand why this check is necessary and why the error occurs, but I am struggling to see the purpose of ngOnChanges if I cannot change the component state from it. In this case, the state change is represented by the 'isValid' boolean flag which causes the borders of the component to turn red.

I have experimented with the following:

  • Using this.changeDetect.detectChanges(), before and after ngOnChanges.
  • Changing 'text' as a getter/setter pair. Moving the validation logic there resulted in the same error. This concept seems akin to ngOnChanges, so the error was expected.
  • ngAfterViewInit does not handle the scenario where the code updates the form (for example, querying from the backend -> filling the edit form). However, no error occurs in this case.
  • Setting the changeDetection strategy as push in the @Component decorator.
  • Attempting timeouts... no changes observed. If a timeout over 1 second is set, nothing happens. If less than 1 second, an error occurs. This behavior is likely related to the development mode check that seems to occur 1 second after the initial change detection.

This situation feels like a common and straightforward use case, which leads me to believe that I may be overlooking something simple here.

Example usage:

<cx-form-text-input validate=".+" label="License Plate*" [(text)]="ticket.registerPlate" format="uppercase"></cx-form-text-input>

Example component:

@Component({
  selector: 'cx-form-text-input',
  templateUrl: './form-text-input.component.html',
  styleUrls: ['./form-text-input.component.scss']
})
export class FormTextInputComponent implements OnChanges {
  @Input()
  public text: string;

  @Output() textChange = new EventEmitter();

  @Input() public validate;

  @Input() public label: string = 'DEFAULT LABEL';

  @Input() public disabled = false;

  @Input() format: string;

  public entry: FormEntry;

  constructor(private form: FormContext, private changeDetect: ChangeDetectorRef) {
    this.entry = form.Join();
  }

  private applyFormat(text: string): string {
    switch (this.format) {
      case 'uppercase':
        return text.toUpperCase();
      default:
        return text;
    }
  }

  public isDisabled(): boolean {
    return this.disabled || this.form.disabled;
  }

  public keyboardEvent() {
    this.entry.isDirty = true;
    this.text = this.applyFormat(this.text);
    this.validateData();

    this.textChange.emit(this.text);
  }

  private validateData() {
    if (!this.validate) {
      this.entry.isValid = true;
      return;
    }

    if (!this.text) {
      this.entry.isValid = false;
      return;
    }

    this.entry.isValid = !!this.text.match(this.validate);
  }

  ngOnChanges(changes: SimpleChanges) {
    this.validateData(); // The pain point...
  }
}

Error:

EXCEPTION: Error in ./EditFormComponent class EditFormComponent - inline template:6:72 caused by: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'.
ORIGINAL EXCEPTION: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'.
ORIGINAL STACKTRACE:

Answer №1

 Inside the ngOnChanges method, it is crucial to call this.validateData() for data validation purposes. Towards the end of the method, invoking this.changeDetect.detectChanges() is necessary to trigger change detection if any modifications were made to the model.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountering an Angular error while trying to use the command "npm run dev:ssr" to observe server-side rendering on the localhost

When I run this command, it starts listening on port 4200 but the page keeps loading without ever fully loading and shows this error in the command prompt: Unhandled Promise rejection: connect ECONNREFUSED 127.0.0.1:6379 ; Zone: <root> ; Task: Promis ...

Choosing a specific option from a dropdown menu in Angular

Whenever I try to update the province of a person using the dropdown list, it doesn't display the old province value. This issue seems to be related to the HTML rendering. This dropdown list is a crucial part of the person's information that I f ...

Angular 2 - mastering the art of handling errors

In my Angular 2 application, I have multiple pages that call backend services. My question is how to create a universal error popup component that can display an error message whenever needed across all pages. Can anyone assist me in finding a solution f ...

What is the best way to show summary calculations in the footer of a PrimeNG table?

I'm struggling to figure out how to create a summary function at the end of a PrimeNG p-table. I need to be able to calculate totals or minimum values for specific columns in the table based on the visible rows. My initial attempt involved trying to ...

Error: ChangeDetectorRef provider is missing from the NullInjector

Implementing Angular 5, I have encountered an error while attempting to trigger a function called select() in one component for selection purposes. This function then triggers another function named getqr() in a separate component responsible for printing. ...

Retrieve GPS data source details using Angular 2

In my Angular 2 application, I am looking to access the GPS location of the device. While I am aware that I can utilize window.geolocation.watchposition() to receive updates on the GPS position, I need a way to distinguish the source of this information. ...

Unable to extract numerical value from object using Dropdown (Angular 4)

When I retrieve data from an API, everything works smoothly except when I try to bind my JSON option number value into the [value] tag. Here's an example: SUCCESSFUL (data retrieved from API is selected in the option) <select [(ngModel)]="data.fr ...

TypeScript struggling to recognize specified types when using a type that encompasses various types

I have a defined type structure that looks like this: export type MediaProps = ImageMediaProps | OembedProps; Following that, the types it references are defined as shown below: type SharedMediaProps = { /** Type of media */ type: "image" | "oembed"; ...

Creating dynamic components with constructor parameters in Angular 9

Having trouble passing a value to the constructor in the component generation code. Check out the original code here: https://stackblitz.com/edit/angular-ivy-tcejuo private addComponent(template: string) { class TemplateComponent { @ViewChild( ...

How to identify a click on a link within the current page using Angular

Is there a way to utilize built-in Angular router properties like RouterLinkActive to detect when a link to the current page is clicked? I am looking to implement a function in the footer that will scroll to the top of the page if the current page link is ...

Understanding the significance of the add() operator in RxJS

Can someone clarify the purpose of the add() operator in rxjs? I've seen it mentioned that it includes a teardown function, but there isn't much detail on what exactly a teardown is or why it's necessary. My specific query relates to impleme ...

Having trouble implementing catchError in a unit test for an HttpInterceptor in Angular 11

I am facing challenges in completing a unit test for my HttpInterceptor. The interceptor serves as a global error handler and is set to trigger on catchError(httpResponseError). While the interceptor functions perfectly fine on my website, I am struggling ...

Generic Typescript abstract method error: "the class specifies the property as an instance member, but the extended class defines it as an instance member function."

Upon exploring the code in this playground link, abstract class Base<F extends () => void> { public abstract __call__: F; } type CallSignature<T> = { (): T; (value: T): void; } class Foo<T> extends Base<CallSignature&l ...

What steps can be taken to safeguard data while navigating within the Angular framework?

I am facing an issue with storing an array of items in a service (referred to as cart service) and displaying it in the component (cart.component.ts). The components bgview.component.ts and single.component.ts are involved in selecting individual items, wi ...

Is it possible to capture and generate an AxiosPromise inside a function?

I am looking to make a change in a function that currently returns an AxiosPromise. Here is the existing code: example(){ return api.get(url); } The api.get call returns an object of type AxiosPromise<any>. I would like to modify this function so ...

Ways to merge two arrays into one in React JS

Here are two arrays presented below: const arrayA = { 0: { id: XXXXXXX, name: "test" }, 1: { id: YYYYYYY, name: "example" } } const arrayB = { 0: { id: XXXXXXX, category: "sea", } 1: { id: YYYYY ...

Best practice in Angular 2: The difference between binding an object as a whole versus binding its individual

What is the best approach for a child component when dealing with object properties and change events? Example 1 <parent-component> <child-component [exampleInput]="object.val" (valueChanged)="updateObjectValue($event)"> ...

Implementing a 12-month display using material-ui components

Just starting out with ReactJs, TypeScript, and material-ui. Looking to display something similar to this design: https://i.stack.imgur.com/zIgUH.png Wondering if it's achievable with material-ui. If not, any suggestions for alternatives? Appreciate ...

Navigating to a specific attribute within a higher-level Component

Within my top-level Component, I have a property that is populated with data from an HTTP source. Here is how it is implemented in a file named app.ts: import {UserData} from './services/user-data/UserData'; Component({ selector: 'app& ...

Troubleshooting Angular 5: Interceptor Fails to Intercept Requests

I have a valid JWT token stored in local storage and an interceptor that I borrowed from a tutorial. However, the interceptor is not intercepting requests and adding headers as expected. Here's where I am making a request: https://github.com/Marred/ ...