Angular Fundamentals - Project Guide
Project Overview
Build a Blog Application that demonstrates all Angular fundamentals concepts including components, services, directives, pipes, routing, and forms.
Features
- ✅ User authentication (mock)
- ✅ Blog post listing with pagination
- ✅ Create, edit, delete posts
- ✅ Comment system
- ✅ Search and filter
- ✅ Category management
- ✅ User profiles
- ✅ Responsive design
Setup
# Create new Angular project
ng new blog-app --standalone --routing --style=css
# Navigate to project
cd blog-app
# Install dependencies (if needed)
npm install
# Start development server
ng serve
Project Structure
src/app/
├── core/
│ ├── models/
│ │ ├── post.model.ts
│ │ ├── user.model.ts
│ │ └── comment.model.ts
│ ├── services/
│ │ ├── auth.service.ts
│ │ ├── post.service.ts
│ │ └── comment.service.ts
│ └── guards/
│ └── auth.guard.ts
├── features/
│ ├── home/
│ │ ├── home.component.ts
│ │ └── home.component.html
│ ├── posts/
│ │ ├── post-list/
│ │ ├── post-detail/
│ │ ├── post-form/
│ │ └── post-card/
│ ├── auth/
│ │ ├── login/
│ │ └── register/
│ └── profile/
│ └── profile.component.ts
├── shared/
│ ├── components/
│ │ ├── header/
│ │ ├── footer/
│ │ └── pagination/
│ ├── directives/
│ │ └── highlight.directive.ts
│ └── pipes/
│ ├── truncate.pipe.ts
│ └── time-ago.pipe.ts
├── app.component.ts
├── app.routes.ts
└── app.config.ts
Phase 1: Core Setup (2-3 hours)
1.1 Define Models
// src/app/core/models/user.model.ts
export interface User {
id: number;
username: string;
email: string;
avatar?: string;
bio?: string;
}
// src/app/core/models/post.model.ts
export interface Post {
id: number;
title: string;
content: string;
excerpt: string;
category: string;
authorId: number;
author?: User;
createdAt: Date;
updatedAt: Date;
imageUrl?: string;
tags: string[];
commentCount?: number;
}
// src/app/core/models/comment.model.ts
export interface Comment {
id: number;
postId: number;
userId: number;
user?: User;
content: string;
createdAt: Date;
}
1.2 Create Auth Service
// src/app/core/services/auth.service.ts
import { Injectable, signal } from '@angular/core';
import { Router } from '@angular/router';
import { User } from '../models/user.model';
@Injectable({
providedIn: 'root',
})
export class AuthService {
private currentUserSignal = signal<User | null>(null);
readonly currentUser = this.currentUserSignal.asReadonly();
readonly isAuthenticated = () => this.currentUser() !== null;
constructor(private router: Router) {
this.loadUserFromStorage();
}
login(email: string, password: string): boolean {
// Mock authentication
if (email && password) {
const user: User = {
id: 1,
username: 'johndoe',
email: email,
avatar: 'https://via.placeholder.com/100',
bio: 'Software developer and blogger',
};
this.currentUserSignal.set(user);
localStorage.setItem('currentUser', JSON.stringify(user));
return true;
}
return false;
}
register(username: string, email: string, password: string): boolean {
// Mock registration
const user: User = {
id: Date.now(),
username,
email,
avatar: 'https://via.placeholder.com/100',
};
this.currentUserSignal.set(user);
localStorage.setItem('currentUser', JSON.stringify(user));
return true;
}
logout() {
this.currentUserSignal.set(null);
localStorage.removeItem('currentUser');
this.router.navigate(['/login']);
}
private loadUserFromStorage() {
const userJson = localStorage.getItem('currentUser');
if (userJson) {
this.currentUserSignal.set(JSON.parse(userJson));
}
}
}
1.3 Create Post Service
// src/app/core/services/post.service.ts
import { Injectable, signal, computed } from '@angular/core';
import { Post } from '../models/post.model';
@Injectable({
providedIn: 'root',
})
export class PostService {
private posts = signal<Post[]>(this.getMockPosts());
readonly allPosts = this.posts.asReadonly();
readonly categories = computed(() => {
const categorySet = new Set(this.posts().map(p => p.category));
return Array.from(categorySet);
});
getPostById(id: number): Post | undefined {
return this.posts().find(p => p.id === id);
}
createPost(post: Omit<Post, 'id' | 'createdAt' | 'updatedAt'>): Post {
const newPost: Post = {
...post,
id: Date.now(),
createdAt: new Date(),
updatedAt: new Date(),
};
this.posts.update(posts => [newPost, ...posts]);
return newPost;
}
updatePost(id: number, updates: Partial<Post>) {
this.posts.update(posts =>
posts.map(post =>
post.id === id ? { ...post, ...updates, updatedAt: new Date() } : post
)
);
}
deletePost(id: number) {
this.posts.update(posts => posts.filter(p => p.id !== id));
}
private getMockPosts(): Post[] {
return [
{
id: 1,
title: 'Getting Started with Angular',
content:
'Angular is a powerful framework for building web applications...',
excerpt: 'Learn the basics of Angular development',
category: 'Tutorial',
authorId: 1,
createdAt: new Date('2024-01-15'),
updatedAt: new Date('2024-01-15'),
imageUrl: 'https://via.placeholder.com/800x400',
tags: ['angular', 'typescript', 'web-development'],
commentCount: 5,
},
// Add more mock posts...
];
}
}
1.4 Create Auth Guard
// src/app/core/guards/auth.guard.ts
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
export const authGuard = () => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isAuthenticated()) {
return true;
}
router.navigate(['/login']);
return false;
};
Phase 2: Layout Components (2-3 hours)
2.1 Header Component
ng generate component shared/components/header --standalone
// src/app/shared/components/header/header.component.ts
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink, RouterLinkActive } from '@angular/router';
import { AuthService } from '../../../core/services/auth.service';
@Component({
selector: 'app-header',
standalone: true,
imports: [CommonModule, RouterLink, RouterLinkActive],
template: `
<header class="header">
<div class="container">
<a routerLink="/" class="logo">MyBlog</a>
<nav class="nav">
<a
routerLink="/"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>
Home
</a>
<a routerLink="/posts" routerLinkActive="active">Posts</a>
@if (authService.isAuthenticated()) {
<a routerLink="/posts/new" routerLinkActive="active">New Post</a>
<a routerLink="/profile" routerLinkActive="active">Profile</a>
<button (click)="logout()" class="btn-logout">Logout</button>
} @else {
<a routerLink="/login" routerLinkActive="active">Login</a>
<a routerLink="/register" routerLinkActive="active">Register</a>
}
</nav>
</div>
</header>
`,
styleUrl: './header.component.css',
})
export class HeaderComponent {
authService = inject(AuthService);
logout() {
this.authService.logout();
}
}
Phase 3: Feature Components (4-6 hours)
3.1 Post List Component
ng generate component features/posts/post-list --standalone
// src/app/features/posts/post-list/post-list.component.ts
import { Component, inject, signal, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router';
import { PostService } from '../../../core/services/post.service';
import { PostCardComponent } from '../post-card/post-card.component';
@Component({
selector: 'app-post-list',
standalone: true,
imports: [CommonModule, FormsModule, RouterLink, PostCardComponent],
template: `
<div class="post-list-container">
<div class="controls">
<input
type="text"
[(ngModel)]="searchQuery"
placeholder="Search posts..."
class="search-input"
/>
<select [(ngModel)]="selectedCategory" class="category-select">
<option value="">All Categories</option>
@for (category of postService.categories(); track category) {
<option [value]="category">{{ category }}</option>
}
</select>
</div>
<div class="posts-grid">
@for (post of filteredPosts(); track post.id) {
<app-post-card [post]="post"></app-post-card>
} @empty {
<p class="empty-state">No posts found</p>
}
</div>
</div>
`,
styleUrl: './post-list.component.css',
})
export class PostListComponent {
postService = inject(PostService);
searchQuery = '';
selectedCategory = '';
filteredPosts = computed(() => {
let posts = this.postService.allPosts();
if (this.selectedCategory) {
posts = posts.filter(p => p.category === this.selectedCategory);
}
if (this.searchQuery) {
const query = this.searchQuery.toLowerCase();
posts = posts.filter(
p =>
p.title.toLowerCase().includes(query) ||
p.excerpt.toLowerCase().includes(query)
);
}
return posts;
});
}
3.2 Post Card Component
// src/app/features/posts/post-card/post-card.component.ts
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';
import { Post } from '../../../core/models/post.model';
@Component({
selector: 'app-post-card',
standalone: true,
imports: [CommonModule, RouterLink],
template: `
<article class="post-card">
@if (post.imageUrl) {
<img [src]="post.imageUrl" [alt]="post.title" class="post-image" />
}
<div class="post-content">
<span class="category-badge">{{ post.category }}</span>
<h2>
<a [routerLink]="['/posts', post.id]">{{ post.title }}</a>
</h2>
<p class="excerpt">{{ post.excerpt }}</p>
<div class="post-meta">
<span class="date">{{ post.createdAt | date }}</span>
<span class="comments">{{ post.commentCount }} comments</span>
</div>
<div class="tags">
@for (tag of post.tags; track tag) {
<span class="tag">{{ tag }}</span>
}
</div>
</div>
</article>
`,
styleUrl: './post-card.component.css',
})
export class PostCardComponent {
@Input({ required: true }) post!: Post;
}
Phase 4: Routing Setup (1-2 hours)
// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { authGuard } from './core/guards/auth.guard';
export const routes: Routes = [
{
path: '',
loadComponent: () =>
import('./features/home/home.component').then(m => m.HomeComponent),
},
{
path: 'posts',
loadComponent: () =>
import('./features/posts/post-list/post-list.component').then(
m => m.PostListComponent
),
},
{
path: 'posts/new',
loadComponent: () =>
import('./features/posts/post-form/post-form.component').then(
m => m.PostFormComponent
),
canActivate: [authGuard],
},
{
path: 'posts/:id',
loadComponent: () =>
import('./features/posts/post-detail/post-detail.component').then(
m => m.PostDetailComponent
),
},
{
path: 'login',
loadComponent: () =>
import('./features/auth/login/login.component').then(
m => m.LoginComponent
),
},
{
path: 'register',
loadComponent: () =>
import('./features/auth/register/register.component').then(
m => m.RegisterComponent
),
},
{
path: 'profile',
loadComponent: () =>
import('./features/profile/profile.component').then(
m => m.ProfileComponent
),
canActivate: [authGuard],
},
{
path: '**',
redirectTo: '',
},
];
Implementation Checklist
Phase 1: Core (2-3 hours)
- Define all models (User, Post, Comment)
- Create AuthService with signals
- Create PostService with CRUD operations
- Create CommentService
- Setup auth guard
Phase 2: Layout (2-3 hours)
- Create Header component with navigation
- Create Footer component
- Setup main app layout
- Add responsive design
Phase 3: Features (4-6 hours)
- Home page with featured posts
- Post list with filtering
- Post detail with comments
- Post form (create/edit)
- Login/Register forms
- User profile page
Phase 4: Polish (2-3 hours)
- Custom directives (highlight, autofocus)
- Custom pipes (truncate, time-ago)
- Error handling
- Loading states
- Form validation
Key Learning Objectives
✅ Component architecture and composition ✅ Service-based state management with signals ✅ Routing and navigation ✅ Form handling and validation ✅ Custom directives and pipes ✅ Authentication flow ✅ CRUD operations ✅ Responsive design
Bonus Features
- Dark Mode - Add theme switching
- Markdown Editor - Rich text editing for posts
- Image Upload - Add image upload functionality
- Social Sharing - Share posts on social media
- Pagination - Implement proper pagination
- Infinite Scroll - Load more posts on scroll
Time Estimate
- Core implementation: 10-12 hours
- With styling: 12-15 hours
- With bonus features: 18-20 hours
Testing
Test the following scenarios:
- User registration and login
- Creating, editing, and deleting posts
- Adding comments
- Searching and filtering
- Protected routes
- Responsive design on mobile
Deployment
# Build for production
ng build --configuration production
# Deploy to hosting service (Netlify, Vercel, etc.)
Next Steps
After completing this project:
- Add real API integration
- Implement advanced state management (NgRx)
- Add unit and integration tests
- Optimize performance with OnPush strategy
- Add PWA capabilities