State management stores data that changes over time and shares it across multiple parts of an Angular application. Older applications commonly place a Subject or BehaviorSubject in a service, allowing several components to subscribe to the same state. Dedicated libraries such as NgRx and NGXS build similar data-exchange systems on RxJS. section 5.2 section 1.2.1
Signals offer a lightweight alternative for application-specific shared state. Because signals notify consumers when updated, a service can own data and behavior for a feature while exposing reactive values to multiple components. This keeps state-related work out of components without requiring a dedicated state-management library. section 7.3 section 7.3.4
A signal-based service can separate reactive state from event streams:
- Represent current, derived application data with signals, such as an array of time-off requests and filtered or resolved requests. section 7.3.4
- Represent user actions such as approving, rejecting, or deleting a request as RxJS Subject event streams. section 7.3.4
- Convert and combine those streams so events update the signal-based state in one direction. section 7.3.4
- Inject the service into components, bind its signals in templates, and delegate user actions back to service methods. section 7.3.4
Time-off request service
A TimeOffManagementService centralizes time-off request data, selected request type, and approval, rejection, and deletion behavior. The component exposes the service’s request signals locally and forwards action methods, so it needs no template changes while becoming shorter and easier to understand and test. section 7.3.4 section 7.3.4
Listing 7.13 State management with advanced RxJS interoperability
@Injectable({ providedIn: 'root' })
export class TimeOffManagementService {
private readonly timeOffRequestService = inject(TimeOffRequestService);
deleteRequest$ = new Subject<TimeOffRequest>(); #1
approveRequest$ = new Subject<TimeOffRequest>();
rejectRequest$ = new Subject<TimeOffRequest>();
selectedType = signal<
'Vacation' | 'Sick Leave' | 'Maternity Leave' | 'Paternity Leave' | 'Other' | ''
>((localStorage.getItem('selectedType') as any) ??
''); #2
requests = toSignal( #3
merge( #4
toObservable(this.selectedType), #5
this.deleteRequest$.pipe(switchMap((r) => this.timeOffRequestService.deleteRequest(r.id))), #6
this.approveRequest$.pipe(switchMap((r) => this.timeOffRequestService.approveRequest(r.id))),
this.rejectRequest$.pipe(switchMap((r) => this.timeOffRequestService.rejectRequest(r.id))),
).pipe(
switchMap(() => {
return this.timeOffRequestService
.getRequestsByType( #7
this.selectedType(),
);
})
),
{
initialValue: [] as TimeOffRequest[],
}
);
resolvedRequests = computed(() =>
this.requests().filter((r) => r.status !== 'Pending')
);
constructor() { #8
effect(() => {
localStorage.setItem('selectedType', this.selectedType());
});
}
approveRequest(request: TimeOffRequest) {
this.approveRequest$.next(request); #9
}
rejectRequest(request: TimeOffRequest) {
this.rejectRequest$.next(request);
}
deleteRequest(request: TimeOffRequest) {
this.deleteRequest$.next(request);
}
}
The listing shows a root-provided service that combines a selected-type signal with RxJS action streams to derive request state. section 7.3.4
Listing 7.14 State management with signals used in a component
export class TimeOffManagementComponent {
private readonly timeOffsService = inject(TimeOffManagementService);
requests = this.timeOffsService.requests;
resolvedRequests = this.timeOffsService.resolvedRequests;
selectedType = this.timeOffsService.selectedType;
approveRequest(request: TimeOffRequest) {
this.timeOffsService.approveRequest(request);
}
rejectRequest(request: TimeOffRequest) {
this.timeOffsService.rejectRequest(request);
}
deleteRequest(request: TimeOffRequest) {
this.timeOffsService.deleteRequest(request);
}
}
The component listing shows the intended boundary: read signals from the service and delegate request actions to it. section 7.3.4








