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
- What is my mistake?
- 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:
- Add a
subject
in your service file. - When you get an error, emit the error message to the subject.
- 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.
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;
}
}