CPCODELAB
Angular

Putting It All Together: A Feature Walkthrough

14 min read

Building a Complete Feature

Let's walk through building a small but complete feature: a posts browser that fetches data from an API, displays a list, and shows a detail view. This combines routing, services, HttpClient, signals, and standalone components.

ts
// posts.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface Post { id: number; title: string; body: string; }

@Injectable({ providedIn: 'root' })
export class PostsService {
  private http = inject(HttpClient);
  getAll = (): Observable<Post[]> =>
    this.http.get<Post[]>('https://jsonplaceholder.typicode.com/posts?_limit=10');
  getOne = (id: number): Observable<Post> =>
    this.http.get<Post>(`https://jsonplaceholder.typicode.com/posts/${id}`);
}
ts
// post-list.component.ts
import { Component, inject, signal } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { RouterLink } from '@angular/router';
import { PostsService } from './posts.service';

@Component({
  selector: 'app-post-list',
  standalone: true,
  imports: [AsyncPipe, RouterLink],
  template: `
    <h2>Posts</h2>
    @for (post of posts$ | async; track post.id) {
      <div class="card">
        <h3>{{ post.title }}</h3>
        <p>{{ post.body | slice:0:100 }}...</p>
        <a [routerLink]="['/posts', post.id]">Read more</a>
      </div>
    }
  `
})
export class PostListComponent {
  private svc = inject(PostsService);
  posts$ = this.svc.getAll();
}
ts
// post-detail.component.ts
import { Component, OnInit, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { PostsService, Post } from './posts.service';

@Component({
  selector: 'app-post-detail',
  standalone: true,
  template: `
    @if (post()) {
      <article>
        <h2>{{ post()!.title }}</h2>
        <p>{{ post()!.body }}</p>
      </article>
    } @else {
      <p>Loading...</p>
    }
  `
})
export class PostDetailComponent implements OnInit {
  private route = inject(ActivatedRoute);
  private svc = inject(PostsService);
  post = signal<Post | null>(null);

  ngOnInit() {
    const id = Number(this.route.snapshot.paramMap.get('id'));
    this.svc.getOne(id).subscribe(p => this.post.set(p));
  }
}

This pattern — service for data, signal for local state, @if/@for in templates, RouterLink for navigation — forms the backbone of most Angular feature development. Master this loop and everything else builds naturally on top.

From here, you can extend this foundation with route guards for authentication, interceptors for auth headers, lazy-loaded feature routes for performance, and advanced RxJS operators for complex data flows. Angular's consistent architecture means every new feature follows the same structure — making large codebases predictable and maintainable.

Ready to test yourself?
Practice Angular— quiz & coding exercises