Putting It All Together: A Feature Walkthrough
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.
// 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}`);
}// 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();
}// 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.