This article is about fixing [input] Add example of using mat-error with parent formgroup validation in Angular components
  • 08-Feb-2023
Lightrun Team
Author Lightrun Team
Share
This article is about fixing [input] Add example of using mat-error with parent formgroup validation in Angular components

[input] Add example of using mat-error with parent formgroup validation in Angular components

Lightrun Team
Lightrun Team
08-Feb-2023

Explanation of the problem

The <mat-error> tag is not being displayed when using an email matching validator for email inputs.

Expected Behavior: The error should be displayed.

Current Behavior: The error is not displayed.

Reproduction Steps:

Code: Custom Validator

private matchEmail(AC: AbstractControl) {
    return AC.get('mail').value === AC.get('mailconfirm').value ? null : { mailmismatch: true };
}

Code: Form Group

this.administratifForm = this.fb.group({
        (...),
        mail: this.fb.control('', [Validators.required, Validators.email]),
        mailconfirm: this.fb.control('', [Validators.required]),
        (...),
    }, {
    validator: this.matchEmail,
    },
);

Code: Template

<mat-form-field>
    <input matInput placeholder="Vérification d'email" formControlName="mailconfirm">
    <mat-error *ngIf="administratifForm.get('mailconfirm').hasError('required')">
        Ce champ est requis
    </mat-error>
    <mat-error *ngIf="administratifForm.hasError('mailmismatch')">
        Les adresses mail ne correspondent pas
    </mat-error>
</mat-form-field>

Environment:

  • Angular: 5.0.2
  • Material: 5.0.0-rc0
  • OS: MacOS Sierra
  • TypeScript: N/A
  • Browsers: Firefox

Additional Information:

If the <mat-error> tag is replaced with a <p> tag, it works.

Troubleshooting with the Lightrun Developer Observability Platform

Getting a sense of what’s actually happening inside a live application is a frustrating experience, one that relies mostly on querying and observing whatever logs were written during development.
Lightrun is a Developer Observability Platform, allowing developers to add telemetry to live applications in real-time, on-demand, and right from the IDE.

  • Instantly add logs to, set metrics in, and take snapshots of live applications
  • Insights delivered straight to your IDE or CLI
  • Works where you do: dev, QA, staging, CI/CD, and production

Start for free today

Problem solution for [input] Add example of using mat-error with parent formgroup validation in Angular components

The problem at hand is that the error message in the <mat-error> tag is not being displayed when using an email matching validator for email inputs. The expected behavior is to display the error, however, the current behavior is that no error is displayed.

To resolve this issue, a custom class called ParentErrorStateMatcher is proposed. This class implements the ErrorStateMatcher interface and returns the error state based on various conditions such as the submission of the form, the dirty or touched state of the control, or the invalid state of the control or its parent. By using this class, one can ensure that the error state is set correctly and the error message can be displayed in the <mat-error> tag.

Here’s an example implementation of the ParentErrorStateMatcher class:

export class ParentErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
      const isSubmitted = !!(form && form.submitted);
      const controlTouched = !!(control && (control.dirty || control.touched));
      const controlInvalid = !!(control && control.invalid);
      const parentInvalid = !!(control && control.parent && control.parent.invalid && (control.parent.dirty || control.parent.touched));

      return isSubmitted || (controlTouched && (controlInvalid || parentInvalid));
  }
}

And here’s an example of how to use the ParentErrorStateMatcher class in the template:

<mat-form-field>
  <input matInput formControlName="password_verify" placeholder="Verify Password" type="password" required [errorStateMatcher]="parentErrorStateMatcher">
  <mat-error *ngIf="register_password_verify.invalid">{{ getRegisterPasswordVerifyError() }}</mat-error>
  <mat-error *ngIf="register_password_group.invalid && !register_password_verify.pristine">{{ getRegisterPasswordGroupError() }}</mat-error>
</mat-form-field>

By adding [errorStateMatcher]="parentErrorStateMatcher" to the input, the error message belonging to the group will be displayed.

Other popular problems with Angular components

Problem: Change Detection Performance Issues

One of the most common problems with Angular components is change detection performance issues. When Angular updates the component view based on changes in the component’s data, it runs change detection on every component, which can have a significant impact on application performance when the component tree is large.

Solution:

To mitigate this issue, you can use OnPush change detection strategy, which only runs change detection on a component when an input value changes. You can set the change detection strategy in the component’s metadata using the changeDetection property:

@Component({
  selector: 'app-my-component',
  template: `...`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent {
  ...
}

Another solution is to use the ngDoCheck lifecycle hook to manually control when change detection should be run:

@Component({
  selector: 'app-my-component',
  template: `...`
})
export class MyComponent implements DoCheck {
  ...
  ngDoCheck() {
    // Perform change detection only if a specific condition is met
    if (this.shouldRunChangeDetection) {
      ...
    }
  }
}

Problem: Memory Leaks

Another common issue with Angular components is memory leaks caused by improperly managed subscriptions or leaking DOM elements. Subscriptions to observables should always be unsubscribed in the ngOnDestroy lifecycle hook to prevent memory leaks:

export class MyComponent implements OnDestroy {
  private subscription: Subscription;

  ngOnInit() {
    this.subscription = this.myService.data$.subscribe(data => {
      ...
    });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Memory leaks can also occur when a DOM element is referenced in a component and the element is not removed from the DOM when the component is destroyed.

Solution:

To prevent this issue, make sure to remove any references to DOM elements in the ngOnDestroy lifecycle hook:

export class MyComponent implements OnDestroy {
  private elementRef: ElementRef;

  constructor(elementRef: ElementRef) {
    this.elementRef = elementRef;
  }

  ngOnDestroy() {
    this.elementRef.nativeElement.remove();
  }
}

Problem: Inconsistent Component State

Inconsistent component state can occur when multiple components share the same data or when the state of a component changes in an unexpected way. This can lead to bugs and unexpected behavior in your application.

Solution:

To mitigate this issue, you can use a centralized store, such as NgRx or Akita, to manage the state of your application. This ensures that all components receive the same data and updates in a consistent and predictable manner.

For example, using NgRx, you can create a store and an action to update the state:

// store.ts
export interface State {
  count: number;
}

const initialState: State = {
  count: 0
};

export function reducer(state = initialState, action: any): State {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    default:
      return state;
  }

A brief introduction to Angular components

Angular components are the fundamental building blocks of an Angular application. They define views, which are sets of user interface elements (UI elements) that belong together and have a specific function within the overall application. Components are written in TypeScript and use the Angular framework to manage data and user interactions.

Each component is made up of three parts: an HTML template, a class definition (in TypeScript), and metadata (in the form of decorators). The template defines the UI for the component, the class defines its behavior and state, and the decorators define its metadata, such as its selector, which is used to define the component’s name and how it can be included in other components’ templates. Components can also communicate with each other, with parent components able to pass data to child components via inputs, and child components able to send data back to the parent via outputs. Overall, the use of components in Angular allows for a clean separation of concerns, with each component responsible for a well-defined portion of the overall application.

Most popular use cases for Angular components

  1. Modularizing User Interface Design: Angular components allow developers to modularize their user interface design into smaller, reusable components. This means that developers can break down complex UI designs into smaller, manageable parts and reuse them in other parts of the application. This leads to a more maintainable and scalable codebase.
  2. Managing State and Event Handling: Angular components can be used to manage state and event handling in an application. A component can maintain its own internal state, which can be updated in response to events that occur within the component. This helps to encapsulate the logic for a particular part of the application, making it easier to reason about and manage.
  3. Improved Performance: Angular components are optimized for performance, as they are designed to update only the parts of the UI that have changed. This helps to reduce the amount of DOM manipulation required, leading to faster updates and better overall performance. Additionally, Angular provides advanced change detection algorithms that help to further optimize updates.

Here is a simple example of an Angular component that updates its internal state based on a button click:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>{{title}}</h1>
    <button (click)="updateTitle()">Update Title</button>
  `
})
export class AppComponent {
  title = 'Angular Components';

  updateTitle() {
    this.title = 'Updated Title';
  }
}
Share

It’s Really not that Complicated.

You can actually understand what’s going on inside your live applications.

Try Lightrun’s Playground

Lets Talk!

Looking for more information about Lightrun and debugging?
We’d love to hear from you!
Drop us a line and we’ll get back to you shortly.

By submitting this form, I agree to Lightrun’s Privacy Policy and Terms of Use.