
In this blog, we will delve into a simple and effective State Management technique using the novel Signals architecture introduced in Angular 16. (You can access my blog post, in which I examine the Angular Signals structure in depth → Link)
While Angular’s architecture is inherently rule-based and might seem complex, grasping its core logic reveals a developer-friendly structure. However, when it comes to state management within this seemingly intricate architecture, complexity can inevitably arise. In larger projects, achieving efficient state management calls for a commitment to the principle of simplicity. A pragmatic approach involves focusing solely on managing essential states and avoiding unnecessary intricacies.
To achieve this, leveraging existing libraries like NgRx, Akita, and MobX is an option. However, a recent consensus suggests that, to sidestep the complexities and boilerplate structures introduced by these libraries, a more viable approach may involve utilizing Angular’s injectable services without relying on additional frameworks. This preference is grounded in the reactive nature of injectable services, their provision of initial values, and their ease of writing, development, and maintenance.
Understanding state management terminology like actions, reducers, etc., may not be necessary for projects of any size. Also, NgRx’s use of BehaviorSubject introduces below challenges:
- Requires strong RxJS knowledge.
- This may lead to excessive memory consumption and leaks in long or large data streams.
- Synchronization or reliability issues in scenarios with multiple threads or asynchronous processes.
Considering these, a signal-based architecture offers optimized change detection and synchronization. Choosing this approach, with the ability to snapshot values instantly, could be preferred in state management. Simply design a store using the signal concept, as demonstrated in a scenario where vehicle information entered in one component is listed in another.
VehicleStateManagementService:
import { Injectable, WritableSignal, signal } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class VehicleStateManagementService {
constructor() {}
private vehicles: WritableSignal<{ brand: string; model: string; isElectric: boolean }[]> = signal([]);
addVehicle(model: string, year: number, isElectric: boolean) {
this.vehicles.mutate((data) =>
data.push({
brand: brand,
model: model,
isElectric: isElectric,
})
);
}
getAllVehicles() {
return this.vehicles();
}
}
If you take a look at the above example, in the 9th line, the collection within the signal named vehicles has the vehicle object with the brand and model information added in addVehicle method, and in the getAllVehicles method, the collection, which is the value of this signal, is returned.
Now, let’s look at the components that will use this service — createVehicle and listVehicle:
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { VehicleStateManagementService} from 'src/app/services/vehicle-state-management.service';
@Component({
selector: 'app-create-vehicle',
standalone: true,
imports: [CommonModule],
template: `
<input type="text" #txtBrand placeholder="Brand"> <br>
<input type="text" #txtModel placeholder="Model"> <br>
<input type="checkbox" #chkIsElectric placeholder="IsElectric"> <br>
<button (click)="createVehicle(txtBrand.value, txtModel.value, chkIsElectric.value)">Create Vehicle</button>
`,
})
export class CreateVehicleComponent {
vehicleStateManagementService: VehicleStateManagementService = inject(VehicleStateManagementService);
createVehicle(brand: string, model: string, isElectric:boolean) {
this.vehicleStateManagementService.createVehicle(model, name, isElectric);
}
}
import { Component, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { VehicleStateManagementService} from 'src/app/services/vehicle-state-management.service';
@Component({
selector: 'app-list-vehicle',
standalone: true,
imports: [CommonModule],
template: `
<ul>
<li *ngFor="let vehicle of vehicles">Brand:{{vehicle.brand}}, Model: {{vehicle.model}}, ElectrictyCar:{vehicle.isElectric}} </li>
</ul>
`,
})
export class ListVehicleComponent implements OnInit {
vehicleStateManagementService: VehicleStateManagementService= inject(VehicleStateManagementService);
vehicles: { brand: string, model: string, brand: boolean}[];
ngOnInit() {
this.vehicles = this.VehicleStateManagementService.getVehicles()
}
}
As seen below components, the vehicle information added in the CreateVehicleComponent is dynamically transferred to the ListVehicleComponent through the signal-based service. In other words, the changes resulting from adding a vehicle are instantly reflected in the component responsible for listing.
Conclusion
In this blog, I have demonstrated how state management in Angular applications can be handled with Signals without using any third-party libraries. I particularly emphasized that this approach allows for a simple and straightforward way of managing this process, especially in large projects.
By reducing complexity, this approach enables projects to be developed more effectively and efficiently. I hope you consider using this method in your future projects.
If you found this blog post helpful, please give it a clap. Thanks for reading…
Originally published on Medium by Atakan Korez.

