Home > Software engineering >  How can I display a reusable errors component in this Angular 14 app?
How can I display a reusable errors component in this Angular 14 app?

Time:01-25

I am working on an app in Angular 14.

The app is designed to contain multiple forms in various components which is why I thought it was a good idea to create a reusable component for error messages.

In form.component.ts I have:

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { FormService } from '../services/form.service';

@Component({
  selector: 'app-form',
  templateUrl: './form.component.html',
  styleUrls: ['./form.component.css'],
})
export class FormComponent {
  public errorMessage: any = null;

  // More code

  public sendFormData() {
    this.formService.sendFormData(this.formService.value).subscribe(
      (response) => {},
      (error) => {
        this.errorMessage = error.message;
      }
    );
  }
}

In errors.component.ts I have:

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

@Component({
  selector: 'app-errors',
  template: `<div >{{ errorMessage }}</div>`,
  styles: [
    `
    .alert-error {
      color: #721c24;
      background-color: #f8d7da;
      border-color: #f5c6cb;
      padding: 0.75rem 1.25rem;
      margin-bottom: 1rem;
      text-align: center;
      border: 1px solid transparent;
      border-radius: 0.25rem;
    }
    `,
  ],
})
export class ErrorsComponent {}

I call the abve component in app.component.html on the condition that there are errors:

<app-errors *ngIf="errorMessage"></app-errors>
<app-form></app-form>

There is a Stackblitz HERE.

The problem

In reality, even though there are errors (the errorMessage is different from null), the ErrorsComponent component is no rendered.

Questions

  1. What is my mistake?
  2. What is the most realizable way to fix this problem?

CodePudding user response:

Your problem is due to errorMessage is defined inside of the FormComponent. And it exists only there, you cant reach it that simple outside of this exact component. You need to wire all of them using @Output() and @Input() and probably using eventEmitters.

But, one of the classic approaches is to create a service, that will hold this errorMessage and will provide it to ErrorsComponent.

Very rough example:

Service (provideIn: root as singleton):

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class ErrorService {
  private readonly _errorMessage$ = new BehaviorSubject<string | undefined>(
    undefined
  );

  public readonly errorMessage$ = this._errorMessage$.asObservable();

  public setErrorMessage(value: string | undefined) {
    this._errorMessage$.next(value);
  }

  constructor() {}
}

Error component:

import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { ErrorService } from '../services/error.service';

@Component({
  selector: 'app-errors',
  template: `<div  *ngIf="errorMessage$ | async as errorMessage">{{ errorMessage }}</div>`,
  styles: [
    `
    .alert-error {
      color: #721c24;
      background-color: #f8d7da;
      border-color: #f5c6cb;
      padding: 0.75rem 1.25rem;
      margin-bottom: 1rem;
      text-align: center;
      border: 1px solid transparent;
      border-radius: 0.25rem;
    }
    `,
  ],
})
export class ErrorsComponent {
  public readonly errorMessage$: Observable<string | undefined>;

  constructor(private readonly errorService: ErrorService) {
    this.errorMessage$ = errorService.errorMessage$;
  }
}

Forms component:

import { Component, OnDestroy } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { ErrorService } from '../services/error.service';
import { FormService } from '../services/form.service';

@Component({
  selector: 'app-form',
  templateUrl: './form.component.html',
  styleUrls: ['./form.component.css'],
})
export class FormComponent implements OnDestroy {
  public accountTypes!: any;
  public selectedAccountType: any;

  public form: FormGroup = new FormGroup({
    first_name: new FormControl('', Validators.required),
    last_name: new FormControl('', Validators.required),
    email: new FormControl('', [Validators.required, Validators.email]),
    accountInfo: new FormGroup({
      account_type: new FormControl(''),
    }),
  });

  constructor(
    private readonly formService: FormService,
    private readonly errorService: ErrorService
  ) {}

  ngOnDestroy(): void {
    this.errorService.setErrorMessage(undefined);
  }

  ngOnInit(): void {
    this.getAccountTypes();
  }

  public getAccountTypes() {
    this.formService.getAccountTypes().subscribe((response) => {
      this.accountTypes = response;
      this.form.controls['accountInfo'].patchValue({
        account_type: response[0].value, // use index as per your choice.
      });
    });
  }

  public sendFormData() {
    this.errorService.setErrorMessage(undefined);
    this.formService.sendFormData(this.formService.value).subscribe(
      (response) => {},
      (error) => this.errorService.setErrorMessage(error.message)
    );
  }
}

StackBlitz: click

CodePudding user response:

Your error component needs some work. If you want to make the error component re-usable, you should create a pub/sub method, so that any component can publish an error message, and error component can capture the error and display it. I'd suggest making following changes:

  1. Add a subject in your service file.
  2. When you get an error, emit the error message to the subject.
  3. Subscribe to the subject in error component. You can control the display from error.component, so your app.component is not responsible for rendering the error alert.

Stackblitz demo

service.ts


  public subject = new Subject<any>();
  dataObservable$ = this.subject.asObservable();

...
...

  updateErrorMsg(error: any) {
    this.subject.next(error);
  }

form.component.ts:

public sendFormData() {
    this.formService.sendFormData(this.form.value).subscribe(
      (response) => {
      },
      (error) => {
        this.formService.updateErrorMsg(error.message);
      }
    );
  }

error.component.ts:

<div *ngIf="errorMessage" >{{ errorMessage }}
  <div><button (click)="closeError()">Close</button></div>
</div>`
export class ErrorsComponent implements OnInit {
  errorMessage;

  constructor(private formService: FormService) {}

  ngOnInit() {
    this.subscribeToData();
  }

  subscribeToData() {
    this.formService.dataObservable$.subscribe((data) => (this.errorMessage = data));
  }

  closeError() {
    this.errorMessage = null;
  }
}
  • Related