Skip to main content

State Management

Flo uses @ngrx/component-store for state management with 10+ specialized stores.

Store Architecture

Each feature area has its own ComponentStore:

StoreScopeKey State
GlobalStoreApp-wideAuth, users, activities, bookings, feature flags, config
BlogStoreBlogArticles, categories, authors, comments (Strapi)
DynamicEntitiesStoreEntitiesDynamic entity listing
DynamicEntityCrudStoreEntity CRUDSingle entity editing, validation
TranslationsStorei18nMulti-language entity translations
LocationStoreLocationsVenue/location management
ApiTokenStoreAPI TokensPublic API token management
GalleryStoreMediaImage/video gallery
ProfessionalsStoreStaffProfessional/instructor management
AnalyticsStoreAnalyticsBusiness metrics and KPIs
WebhooksStoreWebhooksWebhook configuration
PreferencesStorePreferencesImmobile user preferences
ImmobileMatchingStoreMatchingImmobile-user match scoring
SidebarStoreNavigationSidebar 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

  1. One store per feature — Don't share stores across unrelated features
  2. Keep state flat — Avoid deeply nested state objects
  3. Derive, don't duplicate — Use selectors for computed values
  4. Side effects in effects — All API calls go through effects with tapResponse
  5. Scoped providers — Provide stores at component level when possible
  6. Never put business logic in components — Always delegate to stores