Educational Reference: Clean 3-layer architecture with modern Angular signals, multiple production-ready domains (Tasks, Logging, Balance Sheet, Workflow Engine), and comprehensive testing.
This is a learning-focused repository demonstrating production-ready Angular patterns through iterative development. Built as a reference for clean architecture, modern signals, and domain-driven design.
- β 3-layer architecture (Data β Service β Presentation)
- β Domain-driven structure (vertical slices)
- β Separation of concerns
- β Repository pattern for data abstraction
- β Signals for reactive state management
- β Computed signals for derived state
- β Standalone components
- β Signal-based component communication
- β Effect for reactive side effects
- β Audit trail / history tracking
- β System-wide logging with pagination, search, filtering
- β Soft delete pattern
- β Time-travel debugging (revert to previous state)
- β Multi-view presentation (List, Kanban, Metrics, Logs)
- β Drag-and-drop with Angular CDK
- β Multi-select with checkboxes
- β Delta-based change tracking (only log what changed)
- β Financial balance sheet tracking with net worth calculation
- β Enterprise workflow engine with status tracking and templates
- β
Type-safe CRUD operations (zero
anycasts) - β Strict immutability enforcement
- β Robust error handling (quota exceeded, corrupted data)
- β Proper object comparison (no JSON.stringify)
- β Unidirectional data flow (no two-way binding)
- β Single Responsibility Principle (SRP)
- β Centralized constants (no magic strings)
- β SSR-friendly (platform checks)
- β Date serialization handling
- β Enum-driven type safety
- β Comprehensive test coverage (72 passing tests for logging domain)
src/app/domains/task/
βββ data/
β βββ task.repository.ts # Data persistence with error handling
βββ service/
β βββ task.service.ts # CRUD orchestration + logging integration
β βββ taskState.service.ts # Reactive state + business logic
β βββ taskHistory.service.ts # History management (SRP)
βββ presentation/
β βββ task/ # Container component
β βββ task-add/ # Form component (delta-based updates)
β βββ task-list/ # List view with filters
β βββ task-kanban/ # Drag-drop board (dumb layer)
βββ task.model.ts # Domain models & types
βββ task.constants.ts # Centralized constants
src/app/domains/logging/
βββ data/
β βββ logging.repository.ts # Log persistence with delete/clear (localStorage)
β βββ logging.repository.spec.ts # 20 tests (CRUD, SSR, errors)
βββ service/
β βββ logging.service.ts # Complete CRUD with auto-initialization
β βββ logging.service.spec.ts # 14 tests (CRUD, ID generation, integration)
β βββ loggingState.service.ts # Reactive state with auto-sorting
β βββ loggingState.service.spec.ts # 13 tests (signals, sorting, reactivity)
βββ presentation/
β βββ logging-list/
β βββ logging-list.component.ts # Log viewer with pagination, search, filters
β βββ logging-list.component.html # Responsive UI with delete/clear actions
β βββ logging-list.component.scss # Material Design styling
β βββ logging-list.component.spec.ts # 25 tests (filtering, pagination, CRUD)
βββ logging.model.ts # Type-safe log models (Info, Warn, Error)
src/app/domains/balance-sheet/
βββ data/
β βββ balance-sheet.repository.ts # Financial data persistence
βββ service/
β βββ balance-sheet.service.ts # Balance sheet CRUD orchestration
β βββ balanceSheetState.service.ts # Reactive financial state management
βββ presentation/
β βββ account-list/ # Asset/liability account management
β βββ balance-form/ # Record account balances
β βββ balance-history/ # Historical balance tracking
β βββ balance-sheet-view/ # Net worth dashboard
βββ balance-sheet.model.ts # Financial domain models
src/app/domains/workflow/
βββ data/
β βββ workflow.repository.ts # Workflow persistence
βββ service/
β βββ workflow.service.ts # Workflow orchestration
β βββ workflowState.service.ts # Reactive workflow state
βββ presentation/
β βββ instance-tracker/ # Active workflow instances
β βββ template-library/ # Reusable workflow templates
βββ workflow.model.ts # Workflow domain models
src/app/shared/utils/
βββ date-formatter.util.ts # Date formatting (timezone-aware)
βββ json-serialization.util.ts # Date serialization
βββ object-comparison.util.ts # Deep equality (no JSON.stringify)
| Layer | Purpose | Technologies |
|---|---|---|
| Data | Persistence abstraction | localStorage, JSON serialization |
| Service | Business logic & state | Signals, computed signals, CRUD |
| Presentation | UI components | Standalone components, CDK |
The logging domain received comprehensive UI/UX polish and architectural improvements:
1. Tab-Based Level Filter with Counts
- Visual tabs replace dropdown:
All (11) | Info (9) | Warn (2) - Context-aware counts based on search results only (not affected by level selection)
- Disabled tabs when count is 0 for better affordance
- Color-coded tabs matching log levels (blue/orange/red)
2. Convenience Logging API
- New methods:
logInfo(),logWarn(),logError() - Cleaner call sites:
this.loggingService.logInfo('Task created', { context, data }) - No need to import
LogLevelenum in other domains - All Task and Balance Sheet domains updated to use new API
3. Production-Ready Features
- Max log retention (1000 logs) with automatic pruning of oldest
- Relative timestamps ("Just now", "2 minutes ago") with absolute tooltip on hover
- Data expansion preview hints (
{3 fields},[5 items]) - Search icon + clear button for better affordance
- Contextual empty states with icons and helpful messages
4. Performance Optimizations
- TrackBy function for efficient list rendering
- Single-pass counting algorithm for log level distribution
- Proper computed signal chain:
logs β searchFilteredLogs β filteredLogs β paginatedLogs
5. Smooth Animations
- CSS transitions for expand/collapse (0.3s ease-out)
- Animates: max-height, opacity, margin, padding
6. Architectural Clarity
- Counts computed from search-filtered logs (presentation logic stays in component)
- Level filter is view-only selection, doesn't affect tab counts
- Clear separation: domain layer owns raw state, presentation owns filtered views
The logging domain received critical production-ready upgrades:
1. Complete CRUD Operations
- Added
delete()method across all layers (repository β state β service) - Added
clear()method for bulk deletion - Proper orchestration: persist to storage first, then update signal state
- Memory leak prevention: cleanup of UI expansion state
2. Pagination System
- Configurable page size (default 5 logs per page)
- Computed signals for
paginatedLogs,totalPages, navigation state - Auto-reset to first page when filters change
- Conditional display (only shows when multiple pages exist)
3. Enhanced UI/UX
- Search across message, context, and data fields
- Filter by log level (Info, Warning, Error)
- Individual delete buttons with confirmation dialogs
- "Clear All" button with confirmation
- Material Icons integration throughout
4. Type Safety & Documentation
- Fixed
data?: anyβdata?: unknownfor proper type checking - Corrected copy-paste documentation errors
- Added comprehensive JSDoc comments
5. Test Coverage
- Created 25 component tests (filtering, pagination, CRUD)
- Total: 72 tests passing (up from 47)
- Coverage: filtering, pagination, expansion, delete/clear operations
6. DDD Compliance
- β Layer separation maintained
- β Signal-based reactive state
- β Unidirectional data flow
- β Single source of truth preserved
The task domain implements 10 production-grade patterns:
Try/catch blocks around all localStorage operations prevent silent failures. Handles quota exceeded, corrupted data, and access failures gracefully.
Zero any casts throughout the codebase. All types properly defined and leveraged for compile-time safety.
Custom deepEquals() utility replaces unreliable JSON.stringify() for proper change detection with Date objects and arrays.
Computed signals ensure derived state (filters, counts, metrics) only recalculates when dependencies change.
Pure signal-based unidirectional data flow. Removed all [(ngModel)] in favor of explicit [value]/(input) bindings.
Zero console.log statements in production code. Development artifacts removed.
All state operations create new objects/arrays. No mutations anywhere (.map(), .filter(), spread operators).
Dedicated TaskHistoryService handles all history operations. TaskService acts as a focused orchestrator.
task.constants.ts eliminates magic strings. All configuration values centralized for maintainability.
Components delegate business logic to services. Pure presentation logic only (rendering, event delegation).
- Create, read, update, delete tasks
- Status tracking (Pending β In Progress β Completed)
- Priority levels (Low, Medium, High)
- Multi-tag support (Bug, Risk, Feature)
- Delta-based updates (only log changed fields)
Every change is tracked:
history: [{
modifiedAt: Date,
changes: {
status: { oldValue: 'pending', newValue: 'in-progress' }
}
}]Production-ready logging with convenience API:
// Clean logging with convenience methods
this.loggingService.logInfo('Task updated', {
context: 'TaskService.update',
data: { taskId: 123, updates: { status: 'completed' } }
});
this.loggingService.logWarn('Account balance low', {
context: 'BalanceService.check',
data: { accountId: 456, balance: 100 }
});
this.loggingService.logError('Validation failed', {
context: 'AccountService.add',
data: { errors: ['Name required'] }
})- Info logs: Task creation, updates
- Warning logs: Soft/hard deletes
- Error logs: Operations on non-existent tasks
- Features: Pagination (5/page), search, level filtering, delete/clear, expandable data
- Auto-sorted: Newest first with timezone-aware timestamps
Task Domain:
- List View: Filterable task list with full history
- Kanban Board: Drag-and-drop columns with sorting
- Metrics Dashboard: Real-time computed statistics
Logging Domain:
- System Logs: Paginated activity log with search, filtering, delete/clear
Balance Sheet Domain:
- Account Management: Track assets and liabilities
- Balance Recording: Historical balance entries
- Net Worth Dashboard: Real-time financial calculations
Workflow Engine:
- Instance Tracker: Monitor active workflows
- Template Library: Reusable workflow definitions
// Single source of truth
private _tasks = signal<Task[]>([])
// Computed derived state
readonly completionRate = computed(() => {
const total = this._tasks().length
const completed = this.completedTaskCount()
return ((completed / total) * 100).toFixed(1) + '%'
})
// Auto-sorted logs (newest first)
readonly logs = computed(() => {
return [...this._logs()].sort((a, b) =>
b.timeStamp.getTime() - a.timeStamp.getTime()
)
})this.taskService.add({
title: 'Fix login bug',
content: 'Users cannot login with special characters',
status: TaskStatus.Pending,
priority: TaskPriority.High,
tags: [TaskTags.Bug]
})export class TaskComponent {
// Reference the signal (don't call it)
taskCount = this.taskState.taskCount
// Template automatically unwraps with ()
// <span>{{ taskCount() }}</span>
}drop(event: CdkDragDrop<any>, newStatus: TaskStatus) {
const task = event.item.data
if (task.status !== newStatus) {
this.taskService.update(task.id, { status: newStatus })
}
}// β BAD: Mutates existing array
toggleTag(tag: TaskTags) {
this.tags.push(tag) // Mutation!
}
// β
GOOD: Creates new array
toggleTag(tag: TaskTags) {
const currentTags = this.newTags()
this.newTags.set([...currentTags, tag]) // Immutable
}saveAll(tasks: Task[]): boolean {
try {
const tasksCopy = tasks.map(task => ({ ...task }))
localStorage.setItem(TASK_STORAGE_KEY, JSON.stringify(tasksCopy))
return true
} catch (error) {
if (error.name === 'QuotaExceededError') {
console.error('Storage quota exceeded')
}
return false // Graceful degradation
}
}- Study
task.model.ts- domain modeling with TypeScript - Review
task.constants.ts- centralized configuration pattern - Read
task.repository.ts- repository pattern with error handling - Explore
task.service.ts- CRUD orchestration
- Analyze
taskState.service.ts- signals + business logic - Study
taskHistory.service.ts- Single Responsibility Principle - Review
object-comparison.util.ts- proper deep equality - Examine
task-add.component.ts- signal-based forms - Study
task-list.component.ts- filtering & computed signals
- Examine
task-kanban.component.ts- CDK drag-drop + dumb components - Understand immutability patterns throughout
- Study history tracking implementation
- Analyze revert/undo functionality
- Review error handling strategies
- Angular 18+ (standalone components)
- TypeScript 5.0+
- Angular CDK (drag-drop)
- Signals API (reactive state)
- localStorage (persistence)
notes-app/
βββ src/app/
β βββ domains/
β β βββ task/ # Task domain (complete with history)
β β βββ logging/ # Logging domain (complete with pagination)
β β βββ balance-sheet/ # Balance sheet domain (complete)
β β βββ workflow/ # Workflow engine domain (complete)
β β βββ notes/ # Notes domain (planned)
β βββ shared/
β β βββ utils/ # Date formatting, serialization, comparison
β βββ layout/ # Header, sidebar, footer
βββ README.md
User Action (Component)
β
TaskService (Business Logic)
β
TaskRepository (Persistence)
β
TaskStateService (State Update)
β
Computed Signals Update
β
Components Auto-Render
Unidirectional flow ensures predictable state updates.
| Pattern | Location | Purpose |
|---|---|---|
| Repository | task.repository.ts, logging.repository.ts |
Abstract storage with error handling |
| Service Layer | task.service.ts, logging.service.ts |
CRUD orchestration |
| State Management | taskState.service.ts, loggingState.service.ts |
Centralized reactive state + business logic |
| Single Responsibility | taskHistory.service.ts |
Dedicated history management |
| Container/Presenter | Task components | Separation of concerns (dumb UI) |
| Computed Properties | Signals | Efficient derived state (sorting, filtering) |
| Immutability | All layers | Spread operators, no mutations |
| Constants Pattern | task.constants.ts |
No magic strings |
| Deep Equality | object-comparison.util.ts |
Proper change detection |
| Unidirectional Data Flow | Components | Explicit bindings, no [(ngModel)] |
| Audit Log | History tracking + system logs | Compliance & debugging |
| Soft Delete | deleted flag |
Data recovery |
| Error Boundary | Repository layer | Graceful localStorage failures |
| Cross-Domain Integration | Task β Logging | Loose coupling via service injection |
| Delta Tracking | task-add.component.ts |
Only log actual changes |
- β Simpler mental model (no subscriptions)
- β Automatic cleanup
- β Fine-grained reactivity
- β Better performance
- β Easy to test (mock each layer)
- β Swap implementations (localStorage β HTTP)
- β Clear responsibilities
- β Scalable architecture
- β Predictable state changes
- β Easier debugging (state never mutates unexpectedly)
- β Enables time-travel debugging
- β Prevents unintended side effects
- β Better performance with change detection
- β Services stay focused (TaskHistoryService vs TaskService)
- β Easier to test in isolation
- β Simpler to reason about
- β More maintainable over time
- β All task code in one place
- β Easy to find related files
- β Could extract to library
- β Team can own entire domain
- Backend API integration
- Real-time sync (WebSockets)
- Enhanced Undo/Redo stack (beyond history revert)
- Keyboard shortcuts
- Export to CSV/JSON
- Task attachments
- Subtasks / hierarchies
- Due dates & reminders
- Unit & integration tests (72 tests for logging domain)
- Unit tests for task domain
- Unit tests for balance-sheet domain
- Unit tests for workflow domain
- Optimistic updates pattern
- Notes domain implementation
Run ng serve for a dev server. Navigate to http://localhost:4200/.
Run ng build to build the project. The build artifacts will be stored in the dist/ directory.
This is a learning repository. Feel free to:
- Fork and experiment
- Open issues with questions
- Submit PRs with improvements
- Use as a reference for your projects
MIT License - Feel free to use this code for learning and reference.
Daniel Hoffman (@dan7hoffman)
Built as an iterative learning exercise to master Angular patterns and clean architecture.
angular typescript signals clean-architecture crud drag-drop state-management domain-driven-design immutability single-responsibility error-handling type-safety unidirectional-data-flow logging audit-trail delta-tracking learning reference best-practices production-ready