A modern Angular implementation of the RealWorld Conduit app with a clean 3-layer architecture.
This project implements a framework-agnostic 3-layer architecture that separates concerns and enables code reuse across different frameworks:
Backend API (Single Source of Truth)
β
Core Services (Atomic, Pure TypeScript)
β
Page Services (Orchestrators, Pure TypeScript)
β
Page Components (Adapters, Angular-specific)
β
UI Components (Presentational)
- Pure TypeScript (no Angular dependencies)
- Atomic API calls (one service per resource)
- Implement TypeScript interfaces
- Reusable across React, Vue, Svelte
- Return
Promise<Data>
- Pure TypeScript (framework-agnostic)
- Orchestrate multiple Core Services
- Transform and combine data
- Return
Promise<PageState> - Export TypeScript types for reuse
- Angular-specific adapters
- Convert
PromiseβSignal - Manage reactive state
- Dispatch actions to Page Services
- Pass data to UI Components
- Presentational components
- Receive data via
@Input() - Emit events via
@Output() - No business logic
- Angular 21 - Latest version with standalone components
- Zoneless Mode - Better performance without Zone.js
- Signals - Modern reactive state management
- TypeScript - Full type safety (no
anytypes) - Vitest - Fast unit testing (96 tests, 99.38% coverage)
- ESLint - Code quality and consistency
src/
βββ core/ # Framework-agnostic
β βββ services/ # Core Services (atomic API calls)
β β βββ articles-api.service.ts
β β βββ tags-api.service.ts
β β βββ user-api.service.ts
β β βββ profiles-api.service.ts
β β βββ comments-api.service.ts
β β βββ mock-api.service.ts # Mock API for development
β β βββ api.interfaces.ts # TypeScript interfaces
β β βββ retry.util.ts # Retry logic with exponential backoff
β β
β βββ page-services/ # Page Services (orchestrators)
β βββ home-page.service.ts
β βββ article-details-page.service.ts
β βββ signin-page.service.ts
β βββ signup-page.service.ts
β βββ settings-page.service.ts
β βββ editor-page.service.ts
β βββ profile-page.service.ts
β βββ page-state.types.ts # Generic PageState type
β
βββ app/ # Angular-specific
βββ conduit-pages-*/ # Page Components (adapters)
β βββ component.ts # Adapter: PageService β Signal
β βββ template.html # Orchestrates UI Components
β
βββ components/ # UI Components (presentational)
β βββ conduit-*/
β βββ component.ts # @Input/@Output only
β βββ template.html # Pure presentation
β
βββ app.component.ts # Root component
βββ app.routes.ts # Route configuration
βββ app.service.ts # App-level state
βββ user.service.ts # User authentication state
Core and Page Services are Pure TypeScript with no Angular dependencies:
- β
No
@Injectable()decorator - β No RxJS Observables
- β No Angular HttpClient
- β Can be reused in React, Vue, Svelte
- Zero
anytypes in the codebase - Exported TypeScript interfaces for all entities
- Generic
PageState<TData, TExtra>type - Full IntelliSense support
- Complete mock implementation for development
- No backend required
- Configurable latency simulation
- localStorage persistence for articles and user
- Per-article comments stored in Map by slug
- User authentication persisted across page reloads
- 12 sample articles for pagination testing
- Default test user:
[email protected]/1234 - Isolated state per article for comments
- Realistic data with multiple authors and states
- Automatic retry with exponential backoff
- Applied to all GET operations
- 3 retries: 1s β 2s β 4s delays
- Configurable per operation
- Granular loading states per operation
- Error handling with try/catch
- State updates preserve loading/error states
- Reactive updates with Signals
# Run tests
npm run test
# Run tests with coverage
npm run test:coverage
# Run tests in watch mode
npm run test:watch
# Run E2E tests (Playwright)
npm run test:e2eTest Coverage:
- 120+ tests across 14 test files
- 99.38% statement coverage
- 94.37% branch coverage
- 100% function coverage
Test Structure:
- Unit Tests (Vitest): Core Services and Page Services
- E2E Validation Tests (Vitest): Business logic flows at service level
- E2E Tests (Playwright): Full UI integration tests
E2E Validation Tests (Service Level):
article-flows.e2e-validation.test.ts- Create, edit, delete, view articlesauth-flows.e2e-validation.test.ts- Registration and logincomments-flows.e2e-validation.test.ts- Create and delete commentsfavorite-flows.e2e-validation.test.ts- Favorite and unfavorite articlessocial-flows.e2e-validation.test.ts- Follow/unfollow users, view profilessettings-flows.e2e-validation.test.ts- Update user profile, logout
# Install dependencies
npm install
# Start dev server (uses Mock API)
npm start
# Navigate to http://localhost:4200/By default, the app runs with a Mock API in development mode:
- No backend required
- Instant responses (configurable delay)
- Persistent state in localStorage
- Perfect for frontend development
To use the real backend API:
- Set
environment.production = trueinsrc/environments/environment.ts - Configure API URL in Core Services
- Restart dev server
# Production build
npm run build
# Build artifacts will be in dist/For detailed architecture documentation, see ARCHITECTURE.md
For migration guide and patterns, see MIGRATION-PROMPT.md
All pages use a generic PageState<TData, TExtra> structure:
type PageState<TData, TExtra = {}> = {
data: TData; // Core data
loading: Record<string, boolean>; // Loading states
error: Record<string, string | null>; // Error states
} & TExtra; // Page-specific metadataServices are configured manually in main.ts without @Injectable():
const articlesApi = USE_MOCK
? new MockArticlesApiService()
: new ArticlesApiService();
bootstrapApplication(AppComponent, {
providers: [
{ provide: ArticlesApiService, useValue: articlesApi },
{
provide: HomePageService,
useFactory: () => new HomePageService(articlesApi, tagsApi, userApi)
}
]
});Page Components act as adapters between Page Services and UI:
export class HomeComponent implements OnInit {
private pageService = inject(HomePageService);
state = signal<HomePageState>({
data: { articles: [], tags: [], feeds: [] },
loading: { initial: true },
error: { initial: null }
});
ngOnInit() {
this.pageService.init().then(state => this.state.set(state));
}
onFeedSelected(feed: Feed) {
this.pageService
.onFeedSelected({ feed, state: this.state() })
.then(state => this.state.set(state));
}
}- User interacts with UI Component
- UI Component emits event to Page Component
- Page Component calls Page Service method
- Page Service orchestrates Core Services
- Core Services call Backend API
- Data flows back through the layers
- Page Component updates Signal
- Template reactively updates UI
- ESLint configured for Angular best practices
- TypeScript strict mode enabled
- No
anytypes allowed - 100% test coverage on Page Services
- Consistent naming conventions
- Comprehensive error handling
- Zoneless mode for better performance
- Lazy-loaded routes for faster initial load
- Standalone components reduce bundle size
- Signals for efficient change detection
- Retry logic for resilient API calls
MIT
Version: 3.0.0
Last Updated: 2026-02-26
Status: β
Production Ready