Skip to main content

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

  1. Dark Mode - Add theme switching
  2. Markdown Editor - Rich text editing for posts
  3. Image Upload - Add image upload functionality
  4. Social Sharing - Share posts on social media
  5. Pagination - Implement proper pagination
  6. 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