Blog Platform
Build a modern, full-featured Blog Platform using Angular Signals, standalone components, routing, and Tailwind CSS. This project demonstrates real-world patterns for content management applications!
π― What Youβll Build
Section titled βπ― What Youβll BuildβA complete blog platform with:
- β Blog post listing with pagination
- β Individual post detail pages
- β Create and edit posts
- β Categories and tags
- β Search functionality
- β Responsive design with Tailwind CSS
- β Routing with lazy loading
- β Signal-based state management
- β API integration (assuming APIs are ready)
π Prerequisites
Section titled βπ PrerequisitesβBefore starting this project, make sure you have:
- Node.js (v18 or higher) installed
- Angular CLI installed globally
- Basic understanding of TypeScript and Angular
- Familiarity with REST APIs
For project setup instructions, please refer to:
π Project Setup
Section titled βπ Project SetupβStep 1: Create New Angular Project
Section titled βStep 1: Create New Angular Projectβng new blog-platform --standalone --routing --style=css --strictcd blog-platformStep 2: Install Tailwind CSS
Section titled βStep 2: Install Tailwind CSSβnpm install -D tailwindcss postcss autoprefixernpx tailwindcss initStep 3: Configure Tailwind
Section titled βStep 3: Configure TailwindβUpdate tailwind.config.js:
/** @type {import('tailwindcss').Config} */module.exports = { content: [ "./src/**/*.{html,ts}", ], theme: { extend: {}, }, plugins: [],}Update src/styles.css:
@tailwind base;@tailwind components;@tailwind utilities;Step 4: Project Structure
Section titled βStep 4: Project StructureβCreate the following structure:
src/app/βββ models/β βββ blog.model.tsβββ services/β βββ blog.service.tsβββ pages/β βββ home/β β βββ home.component.tsβ βββ post-detail/β β βββ post-detail.component.tsβ βββ post-form/β βββ post-form.component.tsβββ components/β βββ post-card/β β βββ post-card.component.tsβ βββ header/β β βββ header.component.tsβ βββ search-bar/β βββ search-bar.component.tsβββ app.routes.tsπ Step-by-Step Implementation
Section titled βπ Step-by-Step ImplementationβStep 1: Create Models
Section titled βStep 1: Create ModelsβCreate src/app/models/blog.model.ts:
export interface BlogPost { id: string; title: string; content: string; excerpt: string; author: string; category: string; tags: string[]; imageUrl: string; publishedAt: Date; updatedAt: Date;}
export interface CreatePostDto { title: string; content: string; excerpt: string; category: string; tags: string[]; imageUrl: string;}
export interface PaginatedResponse<T> { data: T[]; total: number; page: number; pageSize: number;}What we created:
BlogPost- Main interface for blog postsCreatePostDto- Data transfer object for creating/updating postsPaginatedResponse- Generic interface for paginated API responses
Step 2: Create Blog Service
Section titled βStep 2: Create Blog ServiceβCreate src/app/services/blog.service.ts:
import { Injectable, signal, computed, inject } from '@angular/core';import { HttpClient, HttpParams } from '@angular/common/http';import { Observable, tap } from 'rxjs';import { BlogPost, CreatePostDto, PaginatedResponse } from '../models/blog.model';
@Injectable({ providedIn: 'root'})export class BlogService { private http = inject(HttpClient); private apiUrl = 'https://api.example.com/posts'; // Replace with your API
// State private posts = signal<BlogPost[]>([]); private currentPost = signal<BlogPost | null>(null); private loading = signal(false); private searchQuery = signal(''); private selectedCategory = signal<string>('all');
// Public read-only signals allPosts = this.posts.asReadonly(); post = this.currentPost.asReadonly(); isLoading = this.loading.asReadonly();
// Computed values filteredPosts = computed(() => { const posts = this.posts(); const query = this.searchQuery().toLowerCase(); const category = this.selectedCategory();
let filtered = posts;
if (category !== 'all') { filtered = filtered.filter(post => post.category === category); }
if (query) { filtered = filtered.filter(post => post.title.toLowerCase().includes(query) || post.excerpt.toLowerCase().includes(query) ); }
return filtered; });
categories = computed(() => { const posts = this.posts(); const categorySet = new Set(posts.map(post => post.category)); return ['all', ...Array.from(categorySet)]; });
// API Methods getPosts(page: number = 1, pageSize: number = 10): Observable<PaginatedResponse<BlogPost>> { this.loading.set(true); const params = new HttpParams() .set('page', page.toString()) .set('pageSize', pageSize.toString());
return this.http.get<PaginatedResponse<BlogPost>>(this.apiUrl, { params }).pipe( tap(response => { this.posts.set(response.data); this.loading.set(false); }) ); }
getPostById(id: string): Observable<BlogPost> { this.loading.set(true); return this.http.get<BlogPost>(`${this.apiUrl}/${id}`).pipe( tap(post => { this.currentPost.set(post); this.loading.set(false); }) ); }
createPost(post: CreatePostDto): Observable<BlogPost> { this.loading.set(true); return this.http.post<BlogPost>(this.apiUrl, post).pipe( tap(newPost => { this.posts.update(posts => [newPost, ...posts]); this.loading.set(false); }) ); }
updatePost(id: string, post: Partial<CreatePostDto>): Observable<BlogPost> { this.loading.set(true); return this.http.put<BlogPost>(`${this.apiUrl}/${id}`, post).pipe( tap(updatedPost => { this.posts.update(posts => posts.map(p => p.id === id ? updatedPost : p) ); this.currentPost.set(updatedPost); this.loading.set(false); }) ); }
deletePost(id: string): Observable<void> { this.loading.set(true); return this.http.delete<void>(`${this.apiUrl}/${id}`).pipe( tap(() => { this.posts.update(posts => posts.filter(p => p.id !== id)); this.loading.set(false); }) ); }
// Filter methods setSearchQuery(query: string): void { this.searchQuery.set(query); }
setCategory(category: string): void { this.selectedCategory.set(category); }}Key features:
- β Signal-based state management
- β Computed values for filtering
- β CRUD operations (Create, Read, Update, Delete)
- β Search and category filtering
- β Loading state management
π Next Steps
Section titled βπ Next StepsβContinue to Part 2: Components to build the UI components!
Whatβs in Part 2:
- Header Component
- Search Bar Component
- Post Card Component
- Home Page
- And more!