Master Angular Computed Signals with a Smart Movie & Series Library 🎬📺
Computed signals in Angular 19 are a game-changer. They allow you to define read-only derived state that reacts automatically to changes in its dependencies — no manual subscriptions or lifecycle trickery.
In this post, we'll build a Movie & Series Library app that uses:
- ✅
signal()
andcomputed()
for reactive and derived state - ✅ Standalone Components
- ✅ Angular Services for domain logic
- ✅ Strongly typed TypeScript for safe, scalable architecture
What is computed()
in Angular?
- It's a read-only signal.
- It recalculates automatically when its dependent signals change.
- It memoizes (caches) the value unless dependencies are modified.
- It allows a clean separation between raw data and derived data.
Use Case: Reactive Media Library
Features:
- Manage a library of Movies and Series.
- Dynamically compute:
- Total count
- Type-based filters
- Favorites
- Use
computed()
to keep everything reactive.
Step 1: Define Models
export interface MediaItem {
id: number;
title: string;
type: 'movie' | 'series';
favorite: boolean;
}
Step 2: MediaService with Signals and Computed
import { Injectable, signal, computed } from '@angular/core';
import { MediaItem } from './media.model';
@Injectable({ providedIn: 'root' })
export class MediaService {
private media = signal<MediaItem[]>([
{ id: 1, title: 'Inception', type: 'movie', favorite: false },
{ id: 2, title: 'Breaking Bad', type: 'series', favorite: true },
{ id: 3, title: 'The Matrix', type: 'movie', favorite: true },
]);
get all() {
return this.media;
}
readonly count = computed(() => this.media().length);
readonly movies = computed(() =>
this.media().filter(m => m.type === 'movie')
);
readonly series = computed(() =>
this.media().filter(m => m.type === 'series')
);
readonly favorites = computed(() =>
this.media().filter(m => m.favorite)
);
toggleFavorite(id: number) {
this.media.update(prev =>
prev.map(item =>
item.id === id ? { ...item, favorite: !item.favorite } : item
)
);
}
add(item: MediaItem) {
this.media.update(prev => [...prev, item]);
}
}
Step 3: Create Standalone Component
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MediaService } from './media.service';
@Component({
selector: 'app-media-library',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
🎬 Media Library
Total Items: {{ media.count() }}
Movies: {{ media.movies().length }}
Series: {{ media.series().length }}
Favorites: {{ media.favorites().length }}
Items
{{ item.title }} ({{ item.type }})
{{ item.favorite ? '★' : '☆' }}
Add New Item
Movie
Series
Add
`
})
export class MediaLibraryComponent {
title = '';
type: 'movie' | 'series' = 'movie';
constructor(public media: MediaService) {}
add() {
if (this.title.trim()) {
this.media.add({
id: Date.now(),
title: this.title.trim(),
type: this.type,
favorite: false
});
this.title = '';
}
}
}
Final Thoughts
Feature | Benefit |
---|---|
signal([]) |
Reactive source of truth |
computed() |
Automatically derived state (cached too!) |
Service pattern | Clean domain logic separation |
Standalone | No NgModules, modern & minimal |
Tips:
- Avoid recalculating in templates — use
computed()
in services. -
effect()
pairs well when you want to trigger logic on changes.
Become a signal ninja. Rely on derived state. Your app will thank you.
Happy coding, and may your signals always stay consistent!💥