Skip to content

Learning repository: Angular task management app with 3-layer architecture, modern signals, CRUD operations, Kanban board, and audit history. Educational reference for clean Angular patterns.

Notifications You must be signed in to change notification settings

dan7hoffman/notes-app

Repository files navigation

Notes App - Angular Learning Repository

Educational Reference: Clean 3-layer architecture with modern Angular signals, multiple production-ready domains (Tasks, Logging, Balance Sheet, Workflow Engine), and comprehensive testing.

Angular TypeScript License

πŸ“š Purpose

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.

🎯 What You'll Learn

Architecture Patterns

  • βœ… 3-layer architecture (Data β†’ Service β†’ Presentation)
  • βœ… Domain-driven structure (vertical slices)
  • βœ… Separation of concerns
  • βœ… Repository pattern for data abstraction

Modern Angular (v16+)

  • βœ… Signals for reactive state management
  • βœ… Computed signals for derived state
  • βœ… Standalone components
  • βœ… Signal-based component communication
  • βœ… Effect for reactive side effects

Advanced Features

  • βœ… 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

Production Patterns

  • βœ… Type-safe CRUD operations (zero any casts)
  • βœ… 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)

πŸ—οΈ Architecture

Task 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

Logging Domain

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)

Balance Sheet Domain

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

Workflow Engine Domain

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

Shared Utilities

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 Responsibilities

Layer Purpose Technologies
Data Persistence abstraction localStorage, JSON serialization
Service Business logic & state Signals, computed signals, CRUD
Presentation UI components Standalone components, CDK

⚑ Recent Architectural Improvements

Logging Domain P1 Enhancements (Latest)

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 LogLevel enum 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

Logging Domain P0 Enhancements

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?: unknown for 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

Task Domain Production Patterns

The task domain implements 10 production-grade patterns:

1. Robust Error Handling

Try/catch blocks around all localStorage operations prevent silent failures. Handles quota exceeded, corrupted data, and access failures gracefully.

2. Full Type Safety

Zero any casts throughout the codebase. All types properly defined and leveraged for compile-time safety.

3. Correct Object Comparison

Custom deepEquals() utility replaces unreliable JSON.stringify() for proper change detection with Date objects and arrays.

4. Efficient Data Derivation

Computed signals ensure derived state (filters, counts, metrics) only recalculates when dependencies change.

5. Consistent State Paradigm

Pure signal-based unidirectional data flow. Removed all [(ngModel)] in favor of explicit [value]/(input) bindings.

6. Clean Code

Zero console.log statements in production code. Development artifacts removed.

7. Enforce Immutability

All state operations create new objects/arrays. No mutations anywhere (.map(), .filter(), spread operators).

8. Isolate History Logic (SRP)

Dedicated TaskHistoryService handles all history operations. TaskService acts as a focused orchestrator.

9. Centralize Constants

task.constants.ts eliminates magic strings. All configuration values centralized for maintainability.

10. Dumb Presentation Layer

Components delegate business logic to services. Pure presentation logic only (rendering, event delegation).


πŸš€ Key Features

1. Task Management

  • 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)

2. Audit History

Every change is tracked:

history: [{
  modifiedAt: Date,
  changes: {
    status: { oldValue: 'pending', newValue: 'in-progress' }
  }
}]

3. System Logging

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

4. Multiple Domains & Views

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

5. Reactive State

// 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()
  )
})

πŸ“– Code Examples

Creating a Task

this.taskService.add({
  title: 'Fix login bug',
  content: 'Users cannot login with special characters',
  status: TaskStatus.Pending,
  priority: TaskPriority.High,
  tags: [TaskTags.Bug]
})

Computed Signal Usage

export class TaskComponent {
  // Reference the signal (don't call it)
  taskCount = this.taskState.taskCount

  // Template automatically unwraps with ()
  // <span>{{ taskCount() }}</span>
}

Drag & Drop Status Update

drop(event: CdkDragDrop<any>, newStatus: TaskStatus) {
  const task = event.item.data
  if (task.status !== newStatus) {
    this.taskService.update(task.id, { status: newStatus })
  }
}

Immutable State Updates

// ❌ 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
}

Error Handling Pattern

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
  }
}

πŸŽ“ Learning Path

Beginner

  1. Study task.model.ts - domain modeling with TypeScript
  2. Review task.constants.ts - centralized configuration pattern
  3. Read task.repository.ts - repository pattern with error handling
  4. Explore task.service.ts - CRUD orchestration

Intermediate

  1. Analyze taskState.service.ts - signals + business logic
  2. Study taskHistory.service.ts - Single Responsibility Principle
  3. Review object-comparison.util.ts - proper deep equality
  4. Examine task-add.component.ts - signal-based forms
  5. Study task-list.component.ts - filtering & computed signals

Advanced

  1. Examine task-kanban.component.ts - CDK drag-drop + dumb components
  2. Understand immutability patterns throughout
  3. Study history tracking implementation
  4. Analyze revert/undo functionality
  5. Review error handling strategies

πŸ› οΈ Technical Stack

  • Angular 18+ (standalone components)
  • TypeScript 5.0+
  • Angular CDK (drag-drop)
  • Signals API (reactive state)
  • localStorage (persistence)

πŸ“¦ Project Structure

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

πŸ”„ Data Flow

User Action (Component)
    ↓
TaskService (Business Logic)
    ↓
TaskRepository (Persistence)
    ↓
TaskStateService (State Update)
    ↓
Computed Signals Update
    ↓
Components Auto-Render

Unidirectional flow ensures predictable state updates.


🎯 Design Patterns Used

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

πŸ“ Key Learnings

Why Signals Over RxJS?

  • βœ… Simpler mental model (no subscriptions)
  • βœ… Automatic cleanup
  • βœ… Fine-grained reactivity
  • βœ… Better performance

Why 3 Layers?

  • βœ… Easy to test (mock each layer)
  • βœ… Swap implementations (localStorage β†’ HTTP)
  • βœ… Clear responsibilities
  • βœ… Scalable architecture

Why Immutability?

  • βœ… Predictable state changes
  • βœ… Easier debugging (state never mutates unexpectedly)
  • βœ… Enables time-travel debugging
  • βœ… Prevents unintended side effects
  • βœ… Better performance with change detection

Why Single Responsibility?

  • βœ… Services stay focused (TaskHistoryService vs TaskService)
  • βœ… Easier to test in isolation
  • βœ… Simpler to reason about
  • βœ… More maintainable over time

Why Domain Structure?

  • βœ… All task code in one place
  • βœ… Easy to find related files
  • βœ… Could extract to library
  • βœ… Team can own entire domain

🚧 Future Enhancements

  • 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

πŸ’» Development

Development server

Run ng serve for a dev server. Navigate to http://localhost:4200/.

Build

Run ng build to build the project. The build artifacts will be stored in the dist/ directory.


πŸ“š Resources


🀝 Contributing

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

πŸ“„ License

MIT License - Feel free to use this code for learning and reference.


πŸŽ“ Author

Daniel Hoffman (@dan7hoffman)

Built as an iterative learning exercise to master Angular patterns and clean architecture.


πŸ”– Tags

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

About

Learning repository: Angular task management app with 3-layer architecture, modern signals, CRUD operations, Kanban board, and audit history. Educational reference for clean Angular patterns.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •