Overview
FlightsStoreService is the central state management service for the Air Tracker application. It manages flight data, filters, selection state, and handles smart polling with dynamic intervals based on backend responses.
This service uses Angular signals for reactive state management, providing automatic change detection and computed values.
Location
src/app/features/flights/services/flights-store.service.ts
Public Signals (Read-only State)
All public signals are read-only and automatically trigger change detection when their values change.
flights
readonly flights: Signal<Flight[]>
Array of all loaded flight objects. Each flight contains position, velocity, and aircraft metadata.
selectedFlightId
readonly selectedFlightId: Signal<string | null>
The ICAO24 identifier of the currently selected flight, or null if no flight is selected.
loading
readonly loading: Signal<boolean>
Indicates whether a flight data request is currently in progress.
error
readonly error: Signal<string | null>
Error message if the last request failed, or null if no error.
warning
readonly warning: Signal<string | null>
Warning message (e.g., when data is outdated), or null if no warning.
lastUpdated
readonly lastUpdated: Signal<Date | null>
Timestamp of the last successful data update.
cacheAgeMs
readonly cacheAgeMs: Signal<number>
Age of the cached data in milliseconds (from backend response).
nextUpdateInMs
readonly nextUpdateInMs: Signal<number>
Time until the next scheduled update in milliseconds.
nextUpdateAtMs
readonly nextUpdateAtMs: Signal<number | null>
Absolute timestamp (in milliseconds) when the next update will occur.
currentPollingIntervalMs
readonly currentPollingIntervalMs: Signal<number>
Current polling interval in milliseconds. Defaults to 8000ms (8 seconds).
Computed Signals (Derived State)
Computed signals automatically recalculate when their dependencies change.
operatorList
readonly operatorList: Signal<string[]>
Unique list of airline operators from all flights, sorted alphabetically. Flights with null operators are grouped as ‘Other’ and sorted to the end.
Example:
['Iberia', 'Ryanair', 'Vueling', 'Other']
Source: flights-store.service.ts:68-80
filteredFlights
readonly filteredFlights: Signal<Flight[]>
Flights filtered by current filter criteria (operator and ground status). Automatically updates when filters or flights change.
Filter Logic:
- Operator filter: Matches exact operator name, or ‘Other’ for null/empty operators
- Ground status filter: ‘all’ (no filter), ‘flying’ (onGround = false), or ‘onGround’ (onGround = true)
Source: flights-store.service.ts:87-107
Public Methods
updateFilters()
Update flight filters (partial merge with current filters).
newFilters
Partial<FlightFilters>
required
Partial filter object containing:
operator: Airline operator name or null
onGround: ‘all’ | ‘flying’ | ‘onGround’
Signature:
updateFilters(newFilters: Partial<FlightFilters>): void
Usage Example:
import { inject, Component } from '@angular/core';
import { FlightsStoreService } from './flights-store.service';
@Component({
selector: 'app-filter-menu',
// ...
})
export class FilterMenuComponent {
store = inject(FlightsStoreService);
filterByOperator(operator: string | null): void {
this.store.updateFilters({ operator });
}
showOnlyFlying(): void {
this.store.updateFilters({ onGround: 'flying' });
}
}
Source: flights-store.service.ts:118-120
setSelectedFlightId()
Set the currently selected flight by ICAO24 identifier.
The ICAO24 identifier of the flight to select, or null to clear selection
Signature:
setSelectedFlightId(id: string | null): void
Usage Example:
// Select a flight
this.store.setSelectedFlightId('a1b2c3');
// Clear selection
this.store.setSelectedFlightId(null);
Source: flights-store.service.ts:126-128
clearSelection()
Clear the current flight selection (convenience method).
Signature:
Usage Example:
import { inject, Component } from '@angular/core';
import { FlightsStoreService } from './flights-store.service';
@Component({
selector: 'app-flight-map',
// ...
})
export class FlightMapComponent {
store = inject(FlightsStoreService);
onMapClick(): void {
// Clear selection when user clicks on empty map area
this.store.clearSelection();
}
}
Source: flights-store.service.ts:133-135
startSmartPolling()
Start smart polling with dynamic intervals based on backend response. The service automatically:
- Fetches live flight data
- Updates all state signals
- Adapts polling interval based on
nextUpdateInMs from backend
- Shows warnings when data is outdated (cache age > 30 seconds)
- Retries on error with 15-second delay
Signature:
startSmartPolling(): void
Usage Example:
import { Component, inject, OnInit } from '@angular/core';
import { FlightsStoreService } from './flights-store.service';
@Component({
selector: 'app-flights-shell',
// ...
})
export class FlightsShellComponent implements OnInit {
protected readonly store = inject(FlightsStoreService);
ngOnInit(): void {
// Start polling when component initializes
this.store.startSmartPolling();
}
}
Source: flights-store.service.ts:142-147
stopPolling()
Stop polling and clean up all resources (subscriptions, timers).
Signature:
Usage Example:
import { Component, inject, OnDestroy } from '@angular/core';
import { FlightsStoreService } from './flights-store.service';
@Component({
selector: 'app-flights-shell',
// ...
})
export class FlightsShellComponent implements OnDestroy {
protected readonly store = inject(FlightsStoreService);
ngOnDestroy(): void {
// Stop polling when component is destroyed
this.store.stopPolling();
}
}
Source: flights-store.service.ts:152-162
Real-World Usage
Display Flight List with Filtering
From flights-list.component.ts:49:
import { Component, inject } from '@angular/core';
import { FlightsStoreService } from '../services/flights-store.service';
@Component({
selector: 'app-flights-list',
template: `
<div class="flight-list">
@for (flight of store.filteredFlights(); track flight.icao24) {
<div class="flight-item">
<span>{{ flight.callsign }}</span>
<span>{{ flight.operator }}</span>
</div>
}
</div>
`
})
export class FlightsListComponent {
store = inject(FlightsStoreService);
}
Track Selected Flight
From flights-shell.component.ts:72-99:
import { Component, effect, inject } from '@angular/core';
import { FlightsStoreService } from '../services/flights-store.service';
@Component({
selector: 'app-flights-shell',
// ...
})
export class FlightsShellComponent {
protected readonly store = inject(FlightsStoreService);
constructor() {
// React to selection changes
effect(() => {
const selectedId = this.store.selectedFlightId();
if (selectedId) {
this.openFlightDetailUI(selectedId);
} else {
this.closeFlightDetailUI();
}
});
// Auto-clear selection when flight is filtered out
effect(() => {
const selectedId = this.store.selectedFlightId();
if (!selectedId) return;
const visible = this.store.filteredFlights()
.some(f => f.icao24 === selectedId);
if (!visible) {
this.store.clearSelection();
}
});
}
}
Filter UI with Operator Dropdown
From flights-filter-menu.component.ts:31:
import { Component, inject } from '@angular/core';
import { FlightsStoreService } from '../../services/flights-store.service';
@Component({
selector: 'app-flights-filter-menu',
template: `
<select (change)="onOperatorChange($event)">
<option value="">All Operators</option>
@for (operator of store.operatorList(); track operator) {
<option [value]="operator">{{ operator }}</option>
}
</select>
`
})
export class FlightsFilterMenuComponent {
store = inject(FlightsStoreService);
onOperatorChange(event: Event): void {
const operator = (event.target as HTMLSelectElement).value;
this.store.updateFilters({
operator: operator || null
});
}
}
Smart Polling Behavior
The service implements intelligent polling with these features:
- Dynamic Intervals: Adapts to backend’s
nextUpdateInMs or pollingIntervalMs
- Cache Age Warnings: Shows warning when data is older than 30 seconds
- Error Recovery: Retries failed requests after 15 seconds
- Resource Cleanup: Automatically cancels in-flight requests when starting new polls
Polling Flow (from flights-store.service.ts:175-202):
private poll(): void {
this._loading.set(true);
this.api.getLiveFlights()
.pipe(
catchError((err) => {
console.error('Polling failed:', err);
this._error.set('Error fetching flights');
this._loading.set(false);
// Retry after 15 seconds
this.pollingTimeoutId = setTimeout(() => this.poll(), 15000);
return of(null);
})
)
.subscribe(response => {
if (!response) return;
this._error.set(null);
this.updateStoreFromResponse(response);
// Schedule next poll based on backend response
const nextPollMs = this.calculateNextPollInterval(response);
this.pollingTimeoutId = setTimeout(() => this.poll(), nextPollMs);
});
}
Dependencies
The service automatically injects:
FlightsApiService - For fetching live flight data from the backend
See Also