Skip to content

Commit 47db5c5

Browse files
Merge pull request #2810 from AnvayKharb/WEB-429-implement-centralized-error-handler-service-for-user-friendly-api-error-messaging
Web 429 implement centralized error handler service for user friendly api error messaging
2 parents 7790bc6 + 8c59c27 commit 47db5c5

File tree

4 files changed

+652
-0
lines changed

4 files changed

+652
-0
lines changed
Lines changed: 384 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,384 @@
1+
# Error Handler Service
2+
3+
## Overview
4+
5+
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.
6+
7+
## How It Works
8+
9+
### Trigger Mechanism
10+
11+
The `ErrorHandlerService` is **manually triggered** by components and services using RxJS's `catchError` operator. It works **alongside** (not replacing) the existing `ErrorHandlerInterceptor`:
12+
13+
#### Existing Interceptor (`error-handler.interceptor.ts`)
14+
15+
- Automatically intercepts **all** HTTP errors globally
16+
- Shows generic error dialog for unhandled errors
17+
- Cannot provide context-specific messages
18+
- Located at: `src/app/core/http/error-handler.interceptor.ts`
19+
20+
#### ErrorHandlerService (This Service)
21+
22+
- **Manually invoked** per API call
23+
- Provides **context-specific** error messages
24+
- Displays user-friendly snackbar notifications
25+
- Extracts Fineract-specific error details
26+
- Gives developers fine-grained control
27+
28+
### When to Use
29+
30+
Use `ErrorHandlerService` when you need:
31+
32+
- Custom error messages for specific operations
33+
- Context-aware error handling (e.g., "User not found", "Loan approval failed")
34+
- Snackbar notifications instead of modal dialogs
35+
- To extract and display Fineract API error messages
36+
37+
The interceptor still catches errors you don't explicitly handle, providing a safety net.
38+
39+
## Problem Solved
40+
41+
**Before:** The app handled API errors inconsistently:
42+
43+
- Some errors were shown directly from the server
44+
- Others were not displayed clearly to users
45+
- Error handling code was duplicated across components
46+
- Poor user experience with technical error messages
47+
48+
**After:** Centralized error handling with:
49+
50+
- Consistent error messaging
51+
- User-friendly error descriptions
52+
- Reusable service across all components
53+
- Better UX with appropriate snackbar notifications
54+
55+
## Features
56+
57+
### 1. HTTP Error Handling
58+
59+
- Automatically converts HTTP status codes into user-friendly messages
60+
- Extracts Fineract-specific error messages
61+
- Handles network/connection errors
62+
- Supports contextual error messages
63+
64+
### 2. Supported Error Codes
65+
66+
| Status Code | Title | Behavior |
67+
| ------------- | ------------------- | ----------------------- |
68+
| 400 | Invalid Request | Shows validation errors |
69+
| 401 | Unauthorized | Session expired message |
70+
| 403 | Access Denied | Permission error |
71+
| 404 | Not Found | Resource not found |
72+
| 409 | Conflict | Duplicate resource |
73+
| 500 | Server Error | Generic server error |
74+
| 503 | Service Unavailable | Service down message |
75+
| Network Error | Connection Error | Connection issues |
76+
77+
### 3. Notification Types
78+
79+
- **Error Notifications**: Red snackbar, top-center, 5 seconds
80+
- **Success Notifications**: Green snackbar, bottom-center, 3 seconds
81+
- **Info Notifications**: Blue snackbar, bottom-center, 4 seconds
82+
83+
## Usage
84+
85+
### Basic Error Handling
86+
87+
```typescript
88+
import { ErrorHandlerService } from '@core/error-handler/error-handler.service';
89+
import { catchError } from 'rxjs/operators';
90+
91+
export class MyComponent {
92+
constructor(private errorHandler: ErrorHandlerService) {}
93+
94+
loadData() {
95+
this.dataService
96+
.getData()
97+
.pipe(catchError((error) => this.errorHandler.handleError(error)))
98+
.subscribe((data) => {
99+
// Handle success
100+
});
101+
}
102+
}
103+
```
104+
105+
### Error Handling with Context
106+
107+
```typescript
108+
loadUser(userId: string) {
109+
this.userService.getUser(userId).pipe(
110+
catchError(error => this.errorHandler.handleError(error, 'User'))
111+
).subscribe(user => {
112+
// Handle success
113+
});
114+
}
115+
// Shows: "Not Found: User not found." for 404 errors
116+
```
117+
118+
### Success Messages
119+
120+
```typescript
121+
saveData() {
122+
this.dataService.save(data).subscribe(
123+
response => {
124+
this.errorHandler.showSuccess('Data saved successfully!');
125+
},
126+
error => {
127+
this.errorHandler.handleError(error);
128+
}
129+
);
130+
}
131+
```
132+
133+
### Info Messages
134+
135+
```typescript
136+
loadData() {
137+
this.errorHandler.showInfo('Loading data, please wait...');
138+
this.dataService.getData().subscribe(/* ... */);
139+
}
140+
```
141+
142+
## Advanced Usage
143+
144+
### Custom Error Handling
145+
146+
```typescript
147+
import { HttpErrorResponse } from '@angular/common/http';
148+
149+
processData() {
150+
this.dataService.process().subscribe(
151+
response => {
152+
this.errorHandler.showSuccess('Processing complete!');
153+
},
154+
(error: HttpErrorResponse) => {
155+
if (error.status === 409) {
156+
// Custom handling for conflicts
157+
this.handleConflict(error);
158+
} else {
159+
this.errorHandler.handleError(error, 'Data processing');
160+
}
161+
}
162+
);
163+
}
164+
```
165+
166+
### Integration with Form Validation
167+
168+
```typescript
169+
submitForm() {
170+
if (this.form.invalid) {
171+
this.errorHandler.showInfo('Please fill all required fields');
172+
return;
173+
}
174+
175+
this.formService.submit(this.form.value).pipe(
176+
catchError(error => this.errorHandler.handleError(error, 'Form submission'))
177+
).subscribe(response => {
178+
this.errorHandler.showSuccess('Form submitted successfully!');
179+
});
180+
}
181+
```
182+
183+
## Styling
184+
185+
The service uses three CSS classes defined in `error-handler.component.scss`:
186+
187+
```scss
188+
.error-snackbar // Red background (#f44336) for errors
189+
.success-snackbar // Green background (#4caf50) for success
190+
.info-snackbar // Blue background (#2196f3) for info
191+
```
192+
193+
These styles are automatically applied based on the notification type. The styles are imported globally in `main.scss` so they work across the entire application.
194+
195+
## API Reference
196+
197+
### Methods
198+
199+
#### `handleError(error: HttpErrorResponse, context?: string): Observable<never>`
200+
201+
Handles HTTP errors and shows user-friendly messages.
202+
203+
**Parameters:**
204+
205+
- `error`: The HTTP error response
206+
- `context` (optional): Additional context for more specific messages
207+
208+
**Returns:** Observable that throws the original error
209+
210+
**Example:**
211+
212+
```typescript
213+
this.errorHandler.handleError(error, 'Client');
214+
```
215+
216+
---
217+
218+
#### `showSuccess(message: string, action?: string): void`
219+
220+
Shows a success message to the user.
221+
222+
**Parameters:**
223+
224+
- `message`: The success message to display
225+
- `action` (optional): Button text (defaults to 'OK')
226+
227+
**Example:**
228+
229+
```typescript
230+
this.errorHandler.showSuccess('Client created successfully!', 'View');
231+
```
232+
233+
---
234+
235+
#### `showInfo(message: string, action?: string): void`
236+
237+
Shows an informational message to the user.
238+
239+
**Parameters:**
240+
241+
- `message`: The info message to display
242+
- `action` (optional): Button text (defaults to 'OK')
243+
244+
**Example:**
245+
246+
```typescript
247+
this.errorHandler.showInfo('Loading data...', 'Dismiss');
248+
```
249+
250+
## Fineract Integration
251+
252+
The service automatically extracts Fineract-specific error messages:
253+
254+
```typescript
255+
// Fineract error structure
256+
{
257+
"errors": [{
258+
"defaultUserMessage": "Client with same name already exists"
259+
}],
260+
"defaultUserMessage": "Validation error"
261+
}
262+
```
263+
264+
The service prioritizes:
265+
266+
1. `errors[0].defaultUserMessage`
267+
2. `defaultUserMessage`
268+
3. Generic fallback message
269+
270+
## Migration Guide
271+
272+
### Before (Old Approach)
273+
274+
```typescript
275+
// Inconsistent error handling
276+
this.clientService.getClient(id).subscribe(
277+
(data) => {
278+
/* success */
279+
},
280+
(error) => {
281+
console.error(error);
282+
alert('Error loading client'); // Poor UX
283+
}
284+
);
285+
```
286+
287+
### After (Centralized Approach)
288+
289+
```typescript
290+
// Consistent error handling
291+
this.clientService
292+
.getClient(id)
293+
.pipe(catchError((error) => this.errorHandler.handleError(error, 'Client')))
294+
.subscribe((data) => {
295+
// Handle success
296+
});
297+
```
298+
299+
## Best Practices
300+
301+
1. **Always provide context** for better error messages:
302+
303+
```typescript
304+
catchError((error) => this.errorHandler.handleError(error, 'Loan Application'));
305+
```
306+
307+
2. **Use appropriate notification types**:
308+
- Errors: Use `handleError()` for API failures
309+
- Success: Use `showSuccess()` for successful operations
310+
- Info: Use `showInfo()` for informational messages
311+
312+
3. **Don't duplicate error handling**: Let the service handle standard errors
313+
314+
4. **Custom handling when needed**: Handle specific error cases before using the service
315+
316+
5. **Combine with loading states**:
317+
```typescript
318+
this.isLoading = true;
319+
this.service
320+
.getData()
321+
.pipe(
322+
finalize(() => (this.isLoading = false)),
323+
catchError((error) => this.errorHandler.handleError(error))
324+
)
325+
.subscribe();
326+
```
327+
328+
## Testing
329+
330+
```typescript
331+
import { TestBed } from '@angular/core/testing';
332+
import { MatSnackBar } from '@angular/material/snack-bar';
333+
import { Router } from '@angular/router';
334+
import { ErrorHandlerService } from './error-handler.service';
335+
336+
describe('ErrorHandlerService', () => {
337+
let service: ErrorHandlerService;
338+
let snackBar: jasmine.SpyObj<MatSnackBar>;
339+
340+
beforeEach(() => {
341+
const snackBarSpy = jasmine.createSpyObj('MatSnackBar', ['open']);
342+
const routerSpy = jasmine.createSpyObj('Router', ['navigate']);
343+
344+
TestBed.configureTestingModule({
345+
providers: [
346+
ErrorHandlerService,
347+
{ provide: MatSnackBar, useValue: snackBarSpy },
348+
{ provide: Router, useValue: routerSpy }]
349+
});
350+
351+
service = TestBed.inject(ErrorHandlerService);
352+
snackBar = TestBed.inject(MatSnackBar) as jasmine.SpyObj<MatSnackBar>;
353+
});
354+
355+
it('should show success message', () => {
356+
service.showSuccess('Test success');
357+
expect(snackBar.open).toHaveBeenCalledWith(
358+
'Test success',
359+
'OK',
360+
jasmine.objectContaining({
361+
panelClass: ['success-snackbar']
362+
})
363+
);
364+
});
365+
});
366+
```
367+
368+
## Related Issues
369+
370+
- **WEB-429**: Implement centralized error handler service for user-friendly API error messaging
371+
372+
## Contributing
373+
374+
When modifying this service:
375+
376+
1. Ensure all HTTP status codes are handled appropriately
377+
2. Update the documentation with new features
378+
3. Maintain consistent snackbar positioning and duration
379+
4. Test with actual Fineract API responses
380+
5. Follow Angular and TypeScript best practices
381+
382+
## License
383+
384+
Licensed under the Apache License, Version 2.0. See the LICENSE file for details.

0 commit comments

Comments
 (0)