In modern Angular development, developers seek better reactivity without excessive boilerplate or dependency on complex state management tools. Angular 16+ introduced Signals—a new reactivity model that enables a more declarative and efficient approach to state tracking in your components.
In this article, we will explore how to use Angular Signals with strings and objects through a real-world example: a reactive user profile editor. We'll demonstrate best practices using signal()
, computed()
, effect()
, and update()
to efficiently manage user data and respond to changes in a declarative style.
What Are Signals?
Signals are reactive primitives introduced in Angular 16 that allow you to manage reactive state with fine-grained control. Unlike RxJS Observables, signals:
- Do not require subscription management.
- Trigger updates automatically in views.
- Are optimized for performance.
Three core APIs include:
-
signal()
: defines a reactive writable value. -
computed()
: creates derived signals based on one or more others. -
effect()
: executes side effects when signals change.
Use Signals with Strings and Objects
In our example, we’ll demonstrate signals using:
- Primitive string types (
firstName
,lastName
). - An object type
UserProfile
, which we will mutate reactively.
Update and Display User Data
We'll build a user profile editor that dynamically updates the display as you type. It will react to:
- Changes in the name fields.
- Email updates.
React to Specific Changes with effect()
We'll use effect()
to monitor signal changes and log a warning when a user sets their email to a known placeholder domain (e.g., @example.com
). This technique is also useful for form validation, logging, analytics, or triggering external updates.
Use computed()
to Derive a Full Name
Rather than manually combining fields every time, we use computed()
to derive the full name reactively. This ensures data consistency without duplication of logic.
Show How to update()
Objects Safely
Instead of replacing an entire object, update()
allows updating nested properties in a controlled and efficient way. This avoids unnecessary object references and re-renders.
Advanced Example: user-profile.component.ts
import { Component, signal, computed, effect, WritableSignal } from '@angular/core';
import { FormsModule } from '@angular/forms';
interface UserProfile {
firstName: string;
lastName: string;
email: string;
}
@Component({
selector: 'app-user-profile',
standalone: true,
imports: [FormsModule], // 👈 required to use ngModel
template: `
User Profile
First Name:
Last Name:
Email:
Full Name: {{ fullName() }}
Email: {{ user().email }}
`,
styles: [
`.profile-card {
background: #f5f5f5;
padding: 1rem;
border-radius: 8px;
width: 320px;
}`,
`label { display: block; margin-bottom: 0.5rem; }`,
`input { width: 100%; padding: 0.25rem; margin-top: 0.25rem; }`
]
})
export class UserProfileComponent {
firstName = signal('Ada');
lastName = signal('Lovelace');
user: WritableSignal<UserProfile> = signal({
firstName: 'Ada',
lastName: 'Lovelace',
email: 'ada@angular.dev'
});
fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
constructor() {
effect(() => {
const currentEmail = this.user().email;
if (currentEmail.endsWith('@example.com')) {
console.warn(`Warning: Placeholder email detected: ${currentEmail}`);
}
});
}
updateFirstName(value: string) {
this.firstName.set(value);
this.user.update(current => ({
...current,
firstName: value
}))
}
updateLastName(value: string) {
this.lastName.set(value);
this.user.update(current => ({
...current,
lastName: value
}))
}
updateEmail(value: string) {
this.lastName.set(value);
this.user.update(current => ({
...current,
email: value
}))
}
}
🔍 Key Concepts in This Example
Feature | Purpose |
---|---|
signal() |
Reactive primitive for both primitives and complex types |
computed() |
Derives values automatically when dependencies change |
effect() |
Executes logic based on reactive value changes |
update() |
Updates part of an object without replacing the whole signal value |
ngModel + signals |
Demonstrates how signals integrate with Angular forms |
Bonus Challenge
For extended learning and refinement:
- Add a signal that tracks form validity (e.g.,
isValidName
,isValidEmail
). - Create a
reset()
method to restore original values using.set()
. - Add a list of name changes using
signal
. - Create a toggle mode (
isEditMode
) with computedreadonly
fields.
Conclusion
Angular Signals offer a powerful and elegant solution for managing state reactively without the overhead of RxJS. With minimal code, you can:
- Track and display changes in real-time
- Create dynamic derived data
- React to changes declaratively with effects
This new paradigm simplifies how we manage component logic and enhances maintainability, especially in complex applications.
Angular's future is reactive — and it's a great time to embrace Signals as your new go-to state primitive.
angular #typescript #signals #frontend #architecture