📜State Management Using Signal in Angular

Tacettin Sertkaya
3 min readNov 30, 2023

Signals offer a new approach to building state management. They can notify consumers of value changes and automatically update dependent values.

We will use signals for state management logic and create a form to submit information that will change the state.

Let's create a service that uses signals to store and manipulate the state of products.

import { Injectable, signal } from '@angular/core';

export interface Product {
name: string;
price: number;
}

@Injectable()
export class ProductService {
products = signal<Product[]>(
[
{ name: 'apple', price: 1.99 },
{ name: 'orange', price: 2.99 },
{ name: 'banana', price: 0.99 },
]
);

constructor() { }


// get the products list.
getProducts() {
return this.products;
}

// add a new product to the products list.
addProduct(product: Product) {
this.products.set([...this.products(),
product
]);
}

// update a product in the products list.
updateProduct(key: number, product: Product) {
this.products.set(this.products().map((item, i) => {
if (i === key) {
return product;
}
return item;
}));
}
// delete a product from the products list.
deleteProduct(key: number) {
this.products.set(this.products().filter((item, i) => i !== key));
}


}

Last, we are going to create a component that uses the service to add, remove and update the product list. The component will send a new product submit value via service to our signals. It will also use signals to display, update and delete the data. That’s it!

<!-- app.component.html -->

<div class="container">
<h1>Products</h1>
<div class="flex-container">
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let product of products(); let i = index">
<td>{{ product.name }}</td>
<td>{{ product.price | number }}</td>
<td class="actions">
<button class="btn-edit" (click)="edit(i, product)">Edit</button>
<button class="btn-remove" (click)="remove(i)">Remove</button>
</td>
</tr>
</tbody>
</table>

<form [formGroup]="form" (ngSubmit)="save()">
<div class="form-group">
<input type="text" formControlName="name" placeholder="Name" />
<div
*ngIf="
f['name'].invalid &&
(f['name'].touched || f['name'].dirty || submitted)
"
class="error-message"
>
Name is required.
</div>
</div>

<div class="form-group">
<input type="number" formControlName="price" placeholder="Price" />
<div
*ngIf="
f['price'].invalid &&
(f['price'].touched || f['price'].dirty || submitted)
"
class="error-message"
>
Price is required and must be a non-negative number.
</div>
</div>

<button class="btn-save" type="submit">Save</button>
<button class="btn-cancel" (click)="reset()">Cancel</button>
</form>
</div>
</div>


// app.component.ts
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet, ReactiveFormsModule],
providers: [ProductService],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
// products list by signal
products = this.productService.getProducts();
form!: FormGroup;
submitted: boolean = false;

constructor(
private productService: ProductService,
private formBuilder: FormBuilder
) {
this.createForm()
}

// add or update a product
save() {
const key = this.form.value.key;
this.submitted = true;
if (key)
this.update();
else
this.add();
}


// add a new product
add() {
if (this.form.invalid) {
return;
}
const product = {
name: this.form.value.name,
price: this.form.value.price
} as Product;

this.productService.addProduct(product);
this.reset();
}


// edit a product
edit(key: number, product: any) {
this.form.patchValue({
key: key,
name: product.name,
price: product.price,
submitted: false
})
}


// update a product
update() {
if (this.form.invalid) {
return;
}
const key = this.form.value.key;
const product = {
name: this.form.value.name,
price: this.form.value.price
} as Product;

this.productService.updateProduct(key, product);
this.reset();

}

// delete a product
remove(key: number) {
this.productService.deleteProduct(key);
}



// Computed signal to calculate the all products total price
totalPrice = computed(() => {
return this.products().reduce((acc, curr) => acc + curr.price, 0);
});


// create form
createForm() {
this.form = this.formBuilder.group({
key: new FormControl(null),
name: new FormControl('', [Validators.required]),
price: new FormControl('', [Validators.required]),
});
}

// reset form
reset = () => {
this.form.reset();
this.submitted = false;
}

// get form controls
get f() { return this.form.controls; }



}

We have defined a signal for the list of items in the products list. This signal is updated whenever a new product is added, an existing item is updated or removed. The signal change triggers a recalculation of the view. This allows for an up-to-date and accurate representation of the products list.

The add method adds a new item to the products by signal. The remove method removes the selected item from the products signal.

Conclusion

Managing state in Angular can be effectively done by utilizing signals. Signals provide a powerful way to handle state changes within an Angular application.

Source Code

GitHub Link: https://github.com/tacettinsertkaya/angular-state-management-with-signal

--

--