The ErrorHandlerService is a centralized error-handling service for the Mifos® X Web App. It provides consistent, user-friendly error messaging across the application by converting HTTP errors into meaningful messages and displaying them through Material Design snackbars.
The ErrorHandlerService is manually triggered by components and services using RxJS's catchError operator. It works alongside (not replacing) the existing ErrorHandlerInterceptor:
- Automatically intercepts all HTTP errors globally
- Shows generic error dialog for unhandled errors
- Cannot provide context-specific messages
- Located at:
src/app/core/http/error-handler.interceptor.ts
- Manually invoked per API call
- Provides context-specific error messages
- Displays user-friendly snackbar notifications
- Extracts Apache Fineract®-specific error details
- Gives developers fine-grained control
Use ErrorHandlerService when you need:
- Custom error messages for specific operations
- Context-aware error handling (e.g., "User not found", "Loan approval failed")
- Snackbar notifications instead of modal dialogs
- To extract and display Apache Fineract® API error messages
The interceptor still catches errors you don't explicitly handle, providing a safety net.
Before: The app handled API errors inconsistently:
- Some errors were shown directly from the server
- Others were not displayed clearly to users
- Error handling code was duplicated across components
- Poor user experience with technical error messages
After: Centralized error handling with:
- Consistent error messaging
- User-friendly error descriptions
- Reusable service across all components
- Better UX with appropriate snackbar notifications
- Automatically converts HTTP status codes into user-friendly messages
- Extracts Apache Fineract®-specific error messages
- Handles network/connection errors
- Supports contextual error messages
| Status Code | Title | Behavior |
|---|---|---|
| 400 | Invalid Request | Shows validation errors |
| 401 | Unauthorized | Session expired message |
| 403 | Access Denied | Permission error |
| 404 | Not Found | Resource not found |
| 409 | Conflict | Duplicate resource |
| 500 | Server Error | Generic server error |
| 503 | Service Unavailable | Service down message |
| Network Error | Connection Error | Connection issues |
- Error Notifications: Red snackbar, top-center, 5 seconds
- Success Notifications: Green snackbar, bottom-center, 3 seconds
- Info Notifications: Blue snackbar, bottom-center, 4 seconds
import { ErrorHandlerService } from '@core/error-handler/error-handler.service';
import { catchError } from 'rxjs/operators';
export class MyComponent {
constructor(private errorHandler: ErrorHandlerService) {}
loadData() {
this.dataService
.getData()
.pipe(catchError((error) => this.errorHandler.handleError(error)))
.subscribe((data) => {
// Handle success
});
}
}loadUser(userId: string) {
this.userService.getUser(userId).pipe(
catchError(error => this.errorHandler.handleError(error, 'User'))
).subscribe(user => {
// Handle success
});
}
// Shows: "Not Found: User not found." for 404 errorssaveData() {
this.dataService.save(data).subscribe(
response => {
this.errorHandler.showSuccess('Data saved successfully!');
},
error => {
this.errorHandler.handleError(error);
}
);
}loadData() {
this.errorHandler.showInfo('Loading data, please wait...');
this.dataService.getData().subscribe(/* ... */);
}import { HttpErrorResponse } from '@angular/common/http';
processData() {
this.dataService.process().subscribe(
response => {
this.errorHandler.showSuccess('Processing complete!');
},
(error: HttpErrorResponse) => {
if (error.status === 409) {
// Custom handling for conflicts
this.handleConflict(error);
} else {
this.errorHandler.handleError(error, 'Data processing');
}
}
);
}submitForm() {
if (this.form.invalid) {
this.errorHandler.showInfo('Please fill all required fields');
return;
}
this.formService.submit(this.form.value).pipe(
catchError(error => this.errorHandler.handleError(error, 'Form submission'))
).subscribe(response => {
this.errorHandler.showSuccess('Form submitted successfully!');
});
}The service uses three CSS classes defined in error-handler.component.scss:
.error-snackbar // Red background (#f44336) for errors
.success-snackbar // Green background (#4caf50) for success
.info-snackbar // Blue background (#2196f3) for infoThese styles are automatically applied based on the notification type. The styles are imported globally in main.scss so they work across the entire application.
Handles HTTP errors and shows user-friendly messages.
Parameters:
error: The HTTP error responsecontext(optional): Additional context for more specific messages
Returns: Observable that throws the original error
Example:
this.errorHandler.handleError(error, 'Client');Shows a success message to the user.
Parameters:
message: The success message to displayaction(optional): Button text (defaults to 'OK')
Example:
this.errorHandler.showSuccess('Client created successfully!', 'View');Shows an informational message to the user.
Parameters:
message: The info message to displayaction(optional): Button text (defaults to 'OK')
Example:
this.errorHandler.showInfo('Loading data...', 'Dismiss');The service automatically extracts Apache Fineract®-specific error messages:
// Apache Fineract® error structure
{
"errors": [{
"defaultUserMessage": "Client with same name already exists"
}],
"defaultUserMessage": "Validation error"
}The service prioritizes:
errors[0].defaultUserMessagedefaultUserMessage- Generic fallback message
// Inconsistent error handling
this.clientService.getClient(id).subscribe(
(data) => {
/* success */
},
(error) => {
console.error(error);
alert('Error loading client'); // Poor UX
}
);// Consistent error handling
this.clientService
.getClient(id)
.pipe(catchError((error) => this.errorHandler.handleError(error, 'Client')))
.subscribe((data) => {
// Handle success
});-
Always provide context for better error messages:
catchError((error) => this.errorHandler.handleError(error, 'Loan Application'));
-
Use appropriate notification types:
- Errors: Use
handleError()for API failures - Success: Use
showSuccess()for successful operations - Info: Use
showInfo()for informational messages
- Errors: Use
-
Don't duplicate error handling: Let the service handle standard errors
-
Custom handling when needed: Handle specific error cases before using the service
-
Combine with loading states:
this.isLoading = true; this.service .getData() .pipe( finalize(() => (this.isLoading = false)), catchError((error) => this.errorHandler.handleError(error)) ) .subscribe();
import { TestBed } from '@angular/core/testing';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
import { ErrorHandlerService } from './error-handler.service';
describe('ErrorHandlerService', () => {
let service: ErrorHandlerService;
let snackBar: jasmine.SpyObj<MatSnackBar>;
beforeEach(() => {
const snackBarSpy = jasmine.createSpyObj('MatSnackBar', ['open']);
const routerSpy = jasmine.createSpyObj('Router', ['navigate']);
TestBed.configureTestingModule({
providers: [
ErrorHandlerService,
{ provide: MatSnackBar, useValue: snackBarSpy },
{ provide: Router, useValue: routerSpy }]
});
service = TestBed.inject(ErrorHandlerService);
snackBar = TestBed.inject(MatSnackBar) as jasmine.SpyObj<MatSnackBar>;
});
it('should show success message', () => {
service.showSuccess('Test success');
expect(snackBar.open).toHaveBeenCalledWith(
'Test success',
'OK',
jasmine.objectContaining({
panelClass: ['success-snackbar']
})
);
});
});- WEB-429: Implement centralized error handler service for user-friendly API error messaging
When modifying this service:
- Ensure all HTTP status codes are handled appropriately
- Update the documentation with new features
- Maintain consistent snackbar positioning and duration
- Test with actual Apache Fineract® API responses
- Follow Angular and TypeScript best practices
Licensed under the Apache License, Version 2.0. See the LICENSE file for details.