Skip to main content

State Management

Flo uses @ngrx/component-store for state management. The app currently defines 80 ComponentStore classes; the tables below list the main ones grouped by area.

Store Architecture​

Shared & Core Stores​

StoreScopeKey State
GlobalStoreApp-wideAuth/session, users, activities, bookings, subscriptions, newsletter, feature flags, app configs
SidebarStoreNavigationVisible sections, mobile tabs, user view, user name/initials
TranslationsStorei18nRuntime translation map, current language, loading/error
ApiTokenStorePublic APIAPI tokens, permissions, loading flags
LocationStoreLocationsLocations, current location, loading
DynamicEntitiesStoreDynamic entitiesAvailable entity types, metadata map, load state
DynamicEntityCrudStoreDynamic entity CRUDEntity rows, total count, metadata, validation
MediaGalleryStoreMedia (shared component)Pending items, view mode, upload progress
ImmobileMatchingStoreImmobiliMatch list, active filter, archived toggle
ImmobiliListStoreImmobiliFlagged media ids, scan issues
CatalogoListStoreCatalogoCreating flag, flagged media ids, scan issues
CatalogoDetailStoreCatalogoCategories, loading

Dashboard Stores​

StoreScopeKey State
HomeStoreDashboard landingHome dashboard payload, loading, error
PrenotazioniStoreBookingsSubscription entitlement, subscription detail
BookingPaymentStoreBooking checkoutCheckout, payment order, loading
SchedulesGridStoreSchedulingWeek start/label, grouped schedules, day options
MyStudioSubscriptionsStoreAccountClient subscriptions, check-ins, overview
StudioPlanCommerceStorePlan commerceStudio plan offers, orders, loading
WebAnalyticsStoreWeb analyticsGA4/GSC status, dashboard, timeseries, breakdowns, config
BusinessAnalyticsStoreBusiness analyticsDashboard, selected months, last refresh

Admin Stores (Amministrazione)​

StoreScopeKey State
AmministrazioneStoreAdmin shellSession invalidation state
GestioneUtentiStoreUsersUsers page, flagged ids, total count
UserDetailStoreUser detailFull user detail, loading
ProfessionalsStoreTeamProfessionals, available users
GalleryStoreGalleries / dynamic contentsGalleries, selection, flagged media, structure lock
BlogStoreBlog (legacy Strapi shell)Articles, categories, authors, comments
CommentModerationStoreBlog commentsComments, reports, totals
NewsletterStoreNewsletterTemplate, previews, template types
ComunicazioneAutomazioniStoreCommunicationTemplate, template types, auto-newsletter config
SendJobsStoreCommunicationSend jobs, pagination, selected job
ContactsStoreContactsContacts, current contact, total records
FormsStorePublic formsForm definitions, submissions, settings
CommerceOrdersStoreCommerceOrders, status filter, pagination
PagamentiStorePaymentsPayment orders, status filter, pagination
FatturazioneStoreInvoicing settingsSettings load/save/test flags
FattureStoreE-invoicesInvoice list, pagination
GestioneAbbonamentiClientiStoreClient subscriptionsSubscriptions, stats, plans, users
BrandKitStoreBrand kitKit, asset slots, saving/uploading
WhitelabelEditorStoreWhitelabel editorBranding config, loading/saving/uploading
WebhooksStoreWebhooksWebhook configuration
GestioneFunzionalitaStoreFeature togglesSubscription features, saving
LocalizzazioneStoreLocalizationLocalization settings, saving
note

BlogStore backs the legacy Strapi-based blog admin shell. The native blog CMS (enable_native_blog) is built on the dynamic-entity infrastructure (article.config.ts, author.config.ts, category.config.ts), not on a dedicated ComponentStore.

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 restore, default-password flag
  • Users — Admin user list, booking user search results
  • Activities — Activities, current activity, studio closures
  • Bookings — Selected date/slots/bookings, booked users
  • Subscriptions — User subscriptions
  • Newsletter — Newsletter subscriptions and import result
  • Feature flags — Loaded pre-auth during app initialization
  • Configuration — App configs, external auth config, public localization settings

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