An Angular service packages application behavior or shared state behind dependency injection. It can handle work that components should not own directly, such as authentication requests, state updates, and asynchronous operations. section 2.3.1 section 7.3.4
In a standalone component, a service can be added to the component’s providers array and then used by the component. A login component, for example, delegates its authentication API call to AuthService rather than embedding that call in the template logic. section 2.3.1
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);
}
}
A root-provided service combines RxJS event streams with signals to centralize time-off request state and actions. Components expose the service’s signals and delegate approve, reject, and delete operations back to it. section 7.3.4
Keep services practical in larger applications by separating responsibilities:
- Share data and behavior across components through a single service. section 7.3.4
- Move asynchronous work into the service so consuming components remain small and easier to test. section 7.3.4
- Use RxJS subjects for events and signals for reactive state derived from those events. section 7.3.4
Services that use inject() need an injection context in unit tests. Configure TestBed with required dependencies, run construction through runInInjectionContext, and use HttpTestingController to mock HTTP calls instead of making real requests. section 8.3.4
