State Management
Flo uses @ngrx/component-store for state management with 10+ specialized stores.
Store Architecture
Each feature area has its own ComponentStore:
| Store | Scope | Key State |
|---|---|---|
GlobalStore | App-wide | Auth, users, activities, bookings, feature flags, config |
BlogStore | Blog | Articles, categories, authors, comments (Strapi) |
DynamicEntitiesStore | Entities | Dynamic entity listing |
DynamicEntityCrudStore | Entity CRUD | Single entity editing, validation |
TranslationsStore | i18n | Multi-language entity translations |
LocationStore | Locations | Venue/location management |
ApiTokenStore | API Tokens | Public API token management |
GalleryStore | Media | Image/video gallery |
ProfessionalsStore | Staff | Professional/instructor management |
AnalyticsStore | Analytics | Business metrics and KPIs |
WebhooksStore | Webhooks | Webhook configuration |
PreferencesStore | Preferences | Immobile user preferences |
ImmobileMatchingStore | Matching | Immobile-user match scoring |
SidebarStore | Navigation | Sidebar configuration and state |
Store Pattern
// State interface
export interface FeatureState {
items: Item[];
loading: boolean;
error: string | null;
}
@Injectable()
export class FeatureStore extends ComponentStore<FeatureState> {
constructor(private service: FeatureService) {
super({ items: [], loading: false, error: null });
}
// Selectors — derive data from state
readonly items$ = this.select(state => state.items);
readonly loading$ = this.select(state => state.loading);
// Updaters — synchronous state mutations
readonly setLoading = this.updater((state, loading: boolean) => ({
...state, loading
}));
// Effects — async operations (API calls)
readonly loadItems = this.effect<void>(trigger$ =>
trigger$.pipe(
tap(() => this.setLoading(true)),
switchMap(() => this.service.getAll().pipe(
tapResponse(
items => this.patchState({ items, loading: false }),
error => this.patchState({ error: error.message, loading: false })
)
))
)
);
}
Usage in Components
@Component({
providers: [FeatureStore], // Scoped to component lifecycle
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FeatureComponent {
private readonly store = inject(FeatureStore);
// Expose store data to template
protected readonly items$ = this.store.items$;
protected readonly loading$ = this.store.loading$;
ngOnInit(): void {
this.store.loadItems();
}
onDelete(item: Item): void {
this.store.deleteItem(item.id);
}
}
GlobalStore
The GlobalStore is the main application state, available throughout the app. It manages:
- Authentication state — Current user, session status
- Users — User list for admin views
- Activities — Activity/service data
- Bookings — Booking state for current user
- Feature flags — Loaded pre-auth during
APP_INITIALIZER - Configuration — App settings from admin panel
It is provided at the root level and injected wherever needed.
Best Practices
- One store per feature — Don't share stores across unrelated features
- Keep state flat — Avoid deeply nested state objects
- Derive, don't duplicate — Use selectors for computed values
- Side effects in effects — All API calls go through effects with
tapResponse - Scoped providers — Provide stores at component level when possible
- Never put business logic in components — Always delegate to stores