10 KiB
10 KiB
Quick Reference Guide - New Improvements
🚀 How to Use New Features
1. Validation Service
Import and inject:
import { ValidationService } from '../../services/validation.service';
constructor(private validationService: ValidationService) {}
Validate a single field:
onFieldChange(field: string, value: any) {
const errors = this.validationService.validateItem({ [field]: value });
if (errors[field]) {
this.snackBar.open(`Error: ${errors[field]}`, 'Close', { duration: 3000 });
return;
}
// Proceed with save
}
Validate entire object:
onSubmit() {
const errors = this.validationService.validateItem(this.item);
if (Object.keys(errors).length > 0) {
const errorMsg = Object.values(errors).join(', ');
this.snackBar.open(`Validation failed: ${errorMsg}`, 'Close', { duration: 4000 });
return;
}
// Proceed with API call
this.apiService.createItem(...);
}
Available validators:
validateRequired(value)- Checks for empty valuesvalidateNumber(value, min?, max?)- Validates numeric inputvalidatePrice(value)- Ensures price >= 0validateQuantity(value)- Validates integer >= 0validateUrl(value)- Checks URL formatvalidateImageUrl(value)- Validates image extensionsvalidateId(value)- Ensures URL-safe ID (2-50 chars)validatePriority(value)- Validates 0-9999validateCurrency(value)- USD, EUR, RUB, GBP, UAHvalidateArrayNotEmpty(arr)- Ensures non-empty array
2. Toast Notification Service
Import and inject:
import { ToastService } from '../../services/toast.service';
constructor(private toast: ToastService) {}
Show notifications:
// Success (green, 2s)
this.toast.success('Item saved successfully!');
// Error (red, 4s)
this.toast.error('Failed to save item');
// Warning (orange, 3s)
this.toast.warning('This action will delete all items');
// Info (blue, 2s)
this.toast.info('Loading data...');
// Custom duration
this.toast.success('Saved!', 5000); // 5 seconds
Best practices:
- Use
success()for completed operations - Use
error()for failures with specific message - Use
warning()before dangerous actions - Use
info()for status updates
3. Loading Skeleton Component
Import in component:
import { LoadingSkeletonComponent } from '../../components/loading-skeleton/loading-skeleton.component';
@Component({
imports: [
// ... other imports
LoadingSkeletonComponent
]
})
Use in template:
<!-- For tree/sidebar -->
@if (loading()) {
<app-loading-skeleton type="tree"></app-loading-skeleton>
} @else {
<!-- Your tree content -->
}
<!-- For card layouts -->
@if (loading()) {
<app-loading-skeleton type="card"></app-loading-skeleton>
} @else {
<!-- Your card content -->
}
<!-- For list views -->
@if (loading()) {
<app-loading-skeleton type="list"></app-loading-skeleton>
} @else {
<!-- Your list content -->
}
<!-- For forms -->
@if (loading()) {
<app-loading-skeleton type="form"></app-loading-skeleton>
} @else {
<!-- Your form content -->
}
Available types:
tree- Nested hierarchy with indentation (sidebar)card- Grid of rectangular cardslist- Vertical list of itemsform- Form fields with labels
4. Enhanced Error Handling
The API service now provides detailed error messages automatically:
Error codes and messages:
- 400 - "Invalid request data"
- 401 - "Unauthorized - please log in"
- 403 - "You don't have permission to perform this action"
- 404 - "Resource not found"
- 409 - "Conflict - resource already exists"
- 422 - "Validation failed"
- 500 - "Internal server error"
- 503 - "Service temporarily unavailable"
Example usage:
this.apiService.createItem(item).subscribe({
next: (created) => {
this.toast.success('Item created!');
},
error: (err) => {
// err.message already contains user-friendly message
this.toast.error(err.message || 'Failed to create item');
}
});
5. Enhanced Confirmation Dialogs
Example with count information:
deleteCategory(node: CategoryNode) {
const subCount = node.children?.length || 0;
const message = subCount > 0
? `Are you sure you want to delete "${node.name}"? This will also delete ${subCount} subcategory(ies) and all their items. This action cannot be undone.`
: `Are you sure you want to delete "${node.name}"? This action cannot be undone.`;
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
data: {
title: 'Delete Category',
message: message,
confirmText: 'Delete',
cancelText: 'Cancel',
dangerous: true // Shows red confirm button
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
// User confirmed, proceed with deletion
}
});
}
Dialog options:
title- Dialog headermessage- Description text (can include HTML)confirmText- Confirm button label (default: "Confirm")cancelText- Cancel button label (default: "Cancel")dangerous- Makes confirm button red (for destructive actions)
🎨 Styling Tips
Toast colors are automatic:
// Green background
this.toast.success('...');
// Red background
this.toast.error('...');
// Orange background
this.toast.warning('...');
// Blue background
this.toast.info('...');
Loading skeletons animate automatically:
- Shimmer effect from left to right
- Gray placeholders that match content structure
- No additional CSS needed
📋 Component Integration Checklist
When adding these features to a new component:
For validation:
- Import
ValidationService - Inject in constructor
- Call validation before API calls
- Display errors via toast or inline
For loading states:
- Import
LoadingSkeletonComponent - Add to
importsarray - Add
loadingsignal - Wrap content with
@if (loading()) { skeleton } @else { content }
For toasts:
- Import
ToastService - Inject in constructor
- Replace MatSnackBar calls with toast methods
- Use appropriate type (success/error/warning/info)
For error handling:
- Handle
errorcallback in subscribe - Use
err.messagefor user-friendly message - Display via toast service
- Consider retry logic for critical operations
🐛 Common Patterns
Pattern 1: Form submission with validation
onSubmit() {
// Validate
const errors = this.validationService.validateItem(this.item());
if (Object.keys(errors).length > 0) {
const errorMsg = Object.values(errors).join(', ');
this.toast.error(`Validation failed: ${errorMsg}`);
return;
}
// Save
this.saving.set(true);
this.apiService.updateItem(this.itemId(), this.item()).subscribe({
next: (updated) => {
this.item.set(updated);
this.toast.success('Item saved successfully!');
this.saving.set(false);
},
error: (err) => {
this.toast.error(err.message || 'Failed to save item');
this.saving.set(false);
}
});
}
Pattern 2: Loading data with skeleton
// Component
loading = signal(true);
ngOnInit() {
this.loadData();
}
loadData() {
this.loading.set(true);
this.apiService.getData().subscribe({
next: (data) => {
this.data.set(data);
this.loading.set(false);
},
error: (err) => {
this.toast.error(err.message || 'Failed to load data');
this.loading.set(false);
}
});
}
// Template
@if (loading()) {
<app-loading-skeleton type="list"></app-loading-skeleton>
} @else {
@for (item of data(); track item.id) {
<div>{{ item.name }}</div>
}
}
Pattern 3: Destructive action with confirmation
deleteItem() {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
data: {
title: 'Delete Item',
message: `Are you sure you want to delete "${this.item().name}"? This action cannot be undone.`,
confirmText: 'Delete',
cancelText: 'Cancel',
dangerous: true
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.apiService.deleteItem(this.itemId()).subscribe({
next: () => {
this.toast.success('Item deleted successfully');
this.router.navigate(['/items']);
},
error: (err) => {
this.toast.error(err.message || 'Failed to delete item');
}
});
}
});
}
✅ Testing Your Implementation
Test validation:
- Try submitting empty form → Should show "Name is required"
- Try negative price → Should show "Price must be at least 0"
- Try invalid quantity → Should show "Quantity must be a non-negative integer"
Test toasts:
- Success action → Should show green toast
- Failed action → Should show red toast
- Multiple toasts → Should queue properly
Test loading skeletons:
- Slow network → Should show skeleton first
- Fast network → Should briefly show skeleton
- Skeleton should match content structure
Test error handling:
- Network error → Should show user-friendly message
- 404 error → Should show "Resource not found"
- 500 error → Should show "Internal server error"
🎯 Migration Guide
Replace old MatSnackBar:
// OLD
this.snackBar.open('Saved!', 'Close', { duration: 2000 });
this.snackBar.open('Error!', 'Close', { duration: 3000 });
// NEW
this.toast.success('Saved!');
this.toast.error('Error!');
Replace old spinners:
<!-- OLD -->
@if (loading()) {
<mat-spinner></mat-spinner>
}
<!-- NEW -->
@if (loading()) {
<app-loading-skeleton type="list"></app-loading-skeleton>
}
Add validation to existing forms:
// Before API call, add:
const errors = this.validationService.validateItem(this.item);
if (Object.keys(errors).length > 0) {
this.toast.error(`Validation errors: ${Object.values(errors).join(', ')}`);
return;
}
📚 Additional Resources
- IMPROVEMENTS.md - Full improvement roadmap
- URGENT-IMPROVEMENTS-COMPLETED.md - Implementation details
- API.md - API documentation
For questions or issues, check the implementation in:
src/app/pages/project-view/- Reference implementationsrc/app/pages/item-editor/- Validation examplessrc/app/services/- Service documentation